@osolmaz/pi-workflows 0.14.0 → 0.15.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 (235) hide show
  1. package/README.md +78 -110
  2. package/dist/builtins/autoimplement.workflow.js +3 -1
  3. package/dist/builtins/autoimplement.workflow.js.map +1 -1
  4. package/dist/builtins/change-verification.workflow.js +7 -2
  5. package/dist/builtins/change-verification.workflow.js.map +1 -1
  6. package/dist/builtins/metadata.d.ts +22 -0
  7. package/dist/builtins/metadata.js +10 -0
  8. package/dist/builtins/metadata.js.map +1 -0
  9. package/dist/builtins/monitor.workflow.js +4 -1
  10. package/dist/builtins/monitor.workflow.js.map +1 -1
  11. package/dist/builtins/sanity-check.workflow.js +4 -1
  12. package/dist/builtins/sanity-check.workflow.js.map +1 -1
  13. package/dist/builtins/workspace-preparation.workflow.js +3 -1
  14. package/dist/builtins/workspace-preparation.workflow.js.map +1 -1
  15. package/dist/controllers/index.d.ts +0 -1
  16. package/dist/controllers/index.js +0 -1
  17. package/dist/controllers/index.js.map +1 -1
  18. package/dist/controllers/sqlite.d.ts +51 -0
  19. package/dist/controllers/sqlite.js +433 -57
  20. package/dist/controllers/sqlite.js.map +1 -1
  21. package/dist/controllers/store.d.ts +1 -0
  22. package/dist/extension/controller-command.d.ts +22 -0
  23. package/dist/extension/controller-command.js +32 -0
  24. package/dist/extension/controller-command.js.map +1 -0
  25. package/dist/extension/index.d.ts +2 -9
  26. package/dist/extension/index.js +876 -3447
  27. package/dist/extension/index.js.map +1 -1
  28. package/dist/extension/message-card.d.ts +13 -0
  29. package/dist/extension/message-card.js +46 -0
  30. package/dist/extension/message-card.js.map +1 -0
  31. package/dist/extension/step-message.d.ts +5 -6
  32. package/dist/extension/step-message.js +41 -52
  33. package/dist/extension/step-message.js.map +1 -1
  34. package/dist/host/child-worker-supervisor.d.ts +50 -0
  35. package/dist/host/child-worker-supervisor.js +159 -0
  36. package/dist/host/child-worker-supervisor.js.map +1 -0
  37. package/dist/host/client.d.ts +48 -0
  38. package/dist/host/client.js +216 -0
  39. package/dist/host/client.js.map +1 -0
  40. package/dist/host/controller-worker-entry.d.ts +2 -0
  41. package/dist/host/controller-worker-entry.js +244 -0
  42. package/dist/host/controller-worker-entry.js.map +1 -0
  43. package/dist/host/controller-worker-protocol.d.ts +36 -0
  44. package/dist/host/controller-worker-protocol.js +49 -0
  45. package/dist/host/controller-worker-protocol.js.map +1 -0
  46. package/dist/host/controller-worker-supervisor.d.ts +21 -0
  47. package/dist/host/controller-worker-supervisor.js +54 -0
  48. package/dist/host/controller-worker-supervisor.js.map +1 -0
  49. package/dist/host/host-entry.d.ts +2 -0
  50. package/dist/host/host-entry.js +23 -0
  51. package/dist/host/host-entry.js.map +1 -0
  52. package/dist/host/processes.d.ts +17 -12
  53. package/dist/host/processes.js +154 -50
  54. package/dist/host/processes.js.map +1 -1
  55. package/dist/host/protocol.d.ts +38 -0
  56. package/dist/host/protocol.js +156 -0
  57. package/dist/host/protocol.js.map +1 -0
  58. package/dist/host/resolver-entry.d.ts +49 -0
  59. package/dist/host/resolver-entry.js +149 -0
  60. package/dist/host/resolver-entry.js.map +1 -0
  61. package/dist/host/rpc-executor.d.ts +14 -3
  62. package/dist/host/rpc-executor.js +63 -30
  63. package/dist/host/rpc-executor.js.map +1 -1
  64. package/dist/host/runner.d.ts +95 -29
  65. package/dist/host/runner.js +2311 -348
  66. package/dist/host/runner.js.map +1 -1
  67. package/dist/host/state.d.ts +174 -0
  68. package/dist/host/state.js +689 -0
  69. package/dist/host/state.js.map +1 -0
  70. package/dist/host/worker-entry.d.ts +10 -0
  71. package/dist/host/worker-entry.js +446 -0
  72. package/dist/host/worker-entry.js.map +1 -0
  73. package/dist/host/worker-protocol.d.ts +31 -0
  74. package/dist/host/worker-protocol.js +122 -0
  75. package/dist/host/worker-protocol.js.map +1 -0
  76. package/dist/host/worker-store.d.ts +80 -0
  77. package/dist/host/worker-store.js +91 -0
  78. package/dist/host/worker-store.js.map +1 -0
  79. package/dist/host/worker-supervisor.d.ts +22 -0
  80. package/dist/host/worker-supervisor.js +54 -0
  81. package/dist/host/worker-supervisor.js.map +1 -0
  82. package/dist/render/canvas.d.ts +2 -1
  83. package/dist/render/canvas.js +40 -15
  84. package/dist/render/canvas.js.map +1 -1
  85. package/dist/render/graph-render.d.ts +7 -3
  86. package/dist/render/graph-render.js +125 -74
  87. package/dist/render/graph-render.js.map +1 -1
  88. package/dist/state/database.js +3 -1
  89. package/dist/state/database.js.map +1 -1
  90. package/dist/state/index.d.ts +1 -0
  91. package/dist/state/index.js +1 -0
  92. package/dist/state/index.js.map +1 -1
  93. package/dist/state/schema.d.ts +1 -1
  94. package/dist/state/schema.js +171 -3
  95. package/dist/state/schema.js.map +1 -1
  96. package/dist/state/viewer.d.ts +46 -0
  97. package/dist/state/viewer.js +249 -0
  98. package/dist/state/viewer.js.map +1 -0
  99. package/dist/viewer/cli.d.ts +1 -1
  100. package/dist/viewer/cli.js +47 -18
  101. package/dist/viewer/cli.js.map +1 -1
  102. package/dist/workflows/composition.js +25 -2
  103. package/dist/workflows/composition.js.map +1 -1
  104. package/dist/workflows/definition.d.ts +3 -1
  105. package/dist/workflows/definition.js +25 -0
  106. package/dist/workflows/definition.js.map +1 -1
  107. package/dist/workflows/engine.d.ts +1 -0
  108. package/dist/workflows/engine.js +157 -42
  109. package/dist/workflows/engine.js.map +1 -1
  110. package/dist/workflows/errors.d.ts +3 -1
  111. package/dist/workflows/errors.js +4 -7
  112. package/dist/workflows/errors.js.map +1 -1
  113. package/dist/workflows/human-decision.d.ts +3 -0
  114. package/dist/workflows/human-decision.js +31 -0
  115. package/dist/workflows/human-decision.js.map +1 -1
  116. package/dist/workflows/index.d.ts +1 -1
  117. package/dist/workflows/index.js +1 -1
  118. package/dist/workflows/index.js.map +1 -1
  119. package/dist/workflows/schema.js +17 -6
  120. package/dist/workflows/schema.js.map +1 -1
  121. package/dist/{viewer → workflows}/session-reducer.d.ts +3 -1
  122. package/dist/{viewer → workflows}/session-reducer.js +4 -0
  123. package/dist/workflows/session-reducer.js.map +1 -0
  124. package/dist/workflows/store.d.ts +87 -1
  125. package/dist/workflows/store.js +892 -93
  126. package/dist/workflows/store.js.map +1 -1
  127. package/dist/workflows/tool-input.d.ts +0 -26
  128. package/dist/workflows/tool-input.js +2 -50
  129. package/dist/workflows/tool-input.js.map +1 -1
  130. package/dist/workflows/types.d.ts +36 -4
  131. package/docs/2026-08-30-out-of-process-workflow-host-plan.md +353 -0
  132. package/docs/CONTROLLERS.md +11 -11
  133. package/docs/DEFERRED_TURNS.md +23 -2
  134. package/docs/SQLITE_STATE.md +41 -31
  135. package/docs/WORKFLOW_HOST.md +452 -0
  136. package/docs/development.md +46 -30
  137. package/docs/live-replay-protocol.md +129 -100
  138. package/docs/plans/2026-08-27-workflow-terminal-restart-plan.md +42 -0
  139. package/docs/plans/2026-08-28-piw-incremental-viewer-plan.md +293 -0
  140. package/docs/plans/piw-viewer-experience-implementation-plan.md +11 -1
  141. package/docs/plans/tui-viewer-implementation-plan.md +5 -0
  142. package/docs/tui-viewer.md +18 -3
  143. package/docs/workflows.md +162 -210
  144. package/examples/workflows/command-batch.workflow.ts +2 -0
  145. package/examples/workflows/shell.workflow.ts +2 -1
  146. package/herdr-plugin.toml +1 -1
  147. package/package.json +1 -1
  148. package/src/builtins/autoimplement.workflow.ts +3 -0
  149. package/src/builtins/change-verification.workflow.ts +7 -2
  150. package/src/builtins/metadata.ts +9 -0
  151. package/src/builtins/monitor.workflow.ts +4 -0
  152. package/src/builtins/sanity-check.workflow.ts +4 -0
  153. package/src/builtins/workspace-preparation.workflow.ts +3 -1
  154. package/src/controllers/index.ts +0 -5
  155. package/src/controllers/sqlite.ts +709 -76
  156. package/src/controllers/store.ts +1 -0
  157. package/src/extension/controller-command.ts +45 -0
  158. package/src/extension/index.ts +944 -4163
  159. package/src/extension/message-card.ts +61 -0
  160. package/src/extension/step-message.ts +58 -63
  161. package/src/host/child-worker-supervisor.ts +183 -0
  162. package/src/host/client.ts +293 -0
  163. package/src/host/controller-worker-entry.ts +311 -0
  164. package/src/host/controller-worker-protocol.ts +104 -0
  165. package/src/host/controller-worker-supervisor.ts +79 -0
  166. package/src/host/host-entry.ts +23 -0
  167. package/src/host/processes.ts +171 -54
  168. package/src/host/protocol.ts +196 -0
  169. package/src/host/resolver-entry.ts +241 -0
  170. package/src/host/rpc-executor.ts +76 -34
  171. package/src/host/runner.ts +2813 -422
  172. package/src/host/state.ts +1160 -0
  173. package/src/host/worker-entry.ts +533 -0
  174. package/src/host/worker-protocol.ts +165 -0
  175. package/src/host/worker-store.ts +229 -0
  176. package/src/host/worker-supervisor.ts +74 -0
  177. package/src/render/canvas.ts +44 -10
  178. package/src/render/graph-render.ts +145 -90
  179. package/src/state/database.ts +2 -1
  180. package/src/state/index.ts +14 -0
  181. package/src/state/schema.ts +171 -3
  182. package/src/state/viewer.ts +356 -0
  183. package/src/viewer/cli.ts +49 -17
  184. package/src/workflows/composition.ts +36 -2
  185. package/src/workflows/definition.ts +32 -0
  186. package/src/workflows/engine.ts +157 -54
  187. package/src/workflows/errors.ts +11 -2
  188. package/src/workflows/human-decision.ts +49 -0
  189. package/src/workflows/index.ts +2 -0
  190. package/src/workflows/schema.ts +19 -6
  191. package/src/{viewer → workflows}/session-reducer.ts +11 -1
  192. package/src/workflows/store.ts +1316 -108
  193. package/src/workflows/tool-input.ts +3 -69
  194. package/src/workflows/types.ts +32 -4
  195. package/dist/controllers/workflow-engine-scheduler.d.ts +0 -29
  196. package/dist/controllers/workflow-engine-scheduler.js +0 -175
  197. package/dist/controllers/workflow-engine-scheduler.js.map +0 -1
  198. package/dist/extension/controller-host.d.ts +0 -48
  199. package/dist/extension/controller-host.js +0 -110
  200. package/dist/extension/controller-host.js.map +0 -1
  201. package/dist/extension/deferred-turn-coordinator.d.ts +0 -32
  202. package/dist/extension/deferred-turn-coordinator.js +0 -143
  203. package/dist/extension/deferred-turn-coordinator.js.map +0 -1
  204. package/dist/extension/deferred-turn.d.ts +0 -44
  205. package/dist/extension/deferred-turn.js +0 -110
  206. package/dist/extension/deferred-turn.js.map +0 -1
  207. package/dist/extension/executor.d.ts +0 -86
  208. package/dist/extension/executor.js +0 -311
  209. package/dist/extension/executor.js.map +0 -1
  210. package/dist/extension/follow-up-coordinator.d.ts +0 -27
  211. package/dist/extension/follow-up-coordinator.js +0 -131
  212. package/dist/extension/follow-up-coordinator.js.map +0 -1
  213. package/dist/extension/recorder.d.ts +0 -84
  214. package/dist/extension/recorder.js +0 -528
  215. package/dist/extension/recorder.js.map +0 -1
  216. package/dist/extension/restart-policy.d.ts +0 -38
  217. package/dist/extension/restart-policy.js +0 -116
  218. package/dist/extension/restart-policy.js.map +0 -1
  219. package/dist/extension/session-events.d.ts +0 -133
  220. package/dist/extension/session-events.js +0 -61
  221. package/dist/extension/session-events.js.map +0 -1
  222. package/dist/extension/terminal-decision.d.ts +0 -51
  223. package/dist/extension/terminal-decision.js +0 -110
  224. package/dist/extension/terminal-decision.js.map +0 -1
  225. package/dist/viewer/session-reducer.js.map +0 -1
  226. package/src/controllers/workflow-engine-scheduler.ts +0 -246
  227. package/src/extension/controller-host.ts +0 -167
  228. package/src/extension/deferred-turn-coordinator.ts +0 -171
  229. package/src/extension/deferred-turn.ts +0 -166
  230. package/src/extension/executor.ts +0 -411
  231. package/src/extension/follow-up-coordinator.ts +0 -151
  232. package/src/extension/recorder.ts +0 -655
  233. package/src/extension/restart-policy.ts +0 -163
  234. package/src/extension/session-events.ts +0 -121
  235. package/src/extension/terminal-decision.ts +0 -172
@@ -19,7 +19,7 @@ The controller runtime provides:
19
19
  - Recoverable records for external effects.
20
20
  - Child workflow runs with stable request keys.
21
21
  - Conditions and generations, with cleanup and structured events.
22
- - Local interactive use through the Pi extension and headless use through the engine API.
22
+ - Local resource control through the Pi extension and reconciliation through the global host.
23
23
 
24
24
  The first production use case is pull request automation. A controller can observe a pull request, start a review or repair workflow, wait for checks, validate the current head, and apply an approved change through deterministic code.
25
25
 
@@ -27,7 +27,7 @@ The first production use case is pull request automation. A controller can obser
27
27
 
28
28
  The graph engine remains the execution layer for finite work. A finite workflow can include another finite workflow in the same run through `includeWorkflow()`. Use a controller child run when work needs an independent retry history, stable request key, parallel lifecycle, or indefinite reconciliation. The graph engine does not import the controller runtime. The controller runtime may start workflows through a narrow scheduler interface.
29
29
 
30
- The Pi extension is a host. It discovers definitions, displays status, and supplies the conversation-backed agent executor while Pi is running. A headless host can use the same controller runtime with another `AgentStepExecutor`.
30
+ The Pi extension is a thin client. It resolves controller initialization in a dedicated child process and sends declarative resource commands to the one global host. The host schedules reconciliation but does not load controller definitions in its event loop. A supervised controller worker loads one definition and proposes bounded state changes through the host.
31
31
 
32
32
  External events are wake-up hints. An event enqueues a resource key and carries no transition command. The reconciler reloads the resource and the external system before deciding what to do.
33
33
 
@@ -155,15 +155,15 @@ export type EffectRecord = {
155
155
 
156
156
  The key names one intended effect. Reusing the key with another request fingerprint is an error. The next reconciliation observes the external system before retrying an existing pending or indeterminate effect. The effect can be treated as effectively once when the provider offers an idempotency token, a conditional request, or a reliable way to observe the requested result. The runtime does not promise generic exactly-once execution.
157
157
 
158
- Mutation policy stays in deterministic effect drivers. Agent workflows return findings or artifacts for deterministic code to check and apply. The in-process Pi host shares its process environment with agent tools, so it does not provide credential isolation. Deployments that require that boundary should put authenticated effects behind a separate broker or controller host.
158
+ Mutation policy stays in deterministic effect drivers. Agent workflows return findings or artifacts for deterministic code to check and apply. Worker processes run as the same operating-system user and are not credential sandboxes. Deployments that require credential isolation should put authenticated effects behind a separate broker.
159
159
 
160
160
  ## Child workflows
161
161
 
162
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 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.
164
+ A child run has one durable execution record. The parent resource points to the current run, and workflow completion enqueues the parent key. The global host runs it through the same queue and supervised worker protocol as other workflows.
165
165
 
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.
166
+ A stopped worker does not make the child failed by itself. The host reads the last committed node and effect state. It resumes pure or idempotent work in a new worker epoch. An uncertain manual effect becomes ambiguous and blocks automatic retry. A changed input still requires a new stable request key.
167
167
 
168
168
  ## Deletion and cleanup
169
169
 
@@ -175,9 +175,9 @@ Controllers should add finalizers only when they own something that needs cleanu
175
175
 
176
176
  A source maps an external event to one or more resource keys. Sources include filesystem watches, webhooks, scheduled polling, and child workflow completion. They share the same enqueue API.
177
177
 
178
- `ControllerManager` sets global and per-controller worker limits. A reconciliation deadline stops lease renewal and requeues the key even when controller code ignores its abort signal. JavaScript cannot stop that non-cooperative promise, so reconciler code must pass the signal to provider calls and keep consequential writes inside guarded effect drivers. The local store supports expiring claims from the start, while the first release can run one process. Leader election belongs in a remote store implementation if several hosts later share the same resources.
178
+ The global host claims controller keys and starts a supervised process group for each active reconciliation. A reconciliation deadline stops and requeues the child even when controller code ignores its abort signal. Reconciler code must still pass the signal to provider calls and keep consequential writes inside guarded effect drivers. One host owns the local database. Distributed leader election remains outside this local runtime.
179
179
 
180
- The Pi extension starts local workers during `session_start` and closes them during `session_shutdown`. It can reconcile only while Pi is running. Another program can host `ControllerManager` through the public engine API. The package does not install a service.
180
+ The extension starts the package host on demand. Reconciliation continues when the Pi session closes. The package installs no operating-system service.
181
181
 
182
182
  ## Observability
183
183
 
@@ -202,13 +202,13 @@ A production controller must follow these rules:
202
202
 
203
203
  The controller API is exported from `@osolmaz/pi-workflows/controllers`. Controller definitions use a `.controller.ts` suffix. Project definitions live under `.pi/controllers/`; global definitions live under `~/.pi/agent/controllers/`.
204
204
 
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`.
205
+ The implementation uses documented Pi extension APIs only. `/controller` lists and inspects resources, applies specs, and requests reconciliation or deletion. There are no extension-local worker start or stop controls.
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 a later worker can create another attempt.
207
+ For `apply`, a source resolver child discovers the named controller, verifies its exported name, computes `initialStatus(spec)`, and hashes the source. The host accepts the proposal only while the path still follows discovery rules and the exact digest still matches. Reconcile code runs only in a supervised controller worker.
208
208
 
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.
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 durable request keys and receipts.
210
210
 
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.
211
+ One global host owns that database for all Pi sessions and projects. The extension and mutating CLI paths are local protocol clients; viewers remain read-only. A controller child without an origin session uses a headless `pi --mode rpc` process for structured agent steps.
212
212
 
213
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.
214
214
 
@@ -219,10 +219,21 @@ The fallback custom message uses this details object:
219
219
  "schema": "pi-workflows.deferred-turn-message.v1",
220
220
  "turnIntentId": "deferred-turn:...",
221
221
  "runId": "20260821T081731Z-autoimplement-407480dd",
222
- "cause": "failed"
222
+ "cause": "failed",
223
+ "presentation": {
224
+ "workflowName": "autoimplement",
225
+ "state": "failed",
226
+ "reasonKind": "maxSteps",
227
+ "restart": {
228
+ "count": 0,
229
+ "limit": 3
230
+ }
231
+ }
223
232
  }
224
233
  ```
225
234
 
235
+ `presentation` contains small, bounded display fields. `reasonKind` and `restart` are present only when the run records provide them. These fields do not replace or shorten the model prompt.
236
+
226
237
  The message type is `pi-workflows-deferred-turn`. Delivery uses:
227
238
 
228
239
  ```ts
@@ -232,7 +243,17 @@ The message type is `pi-workflows-deferred-turn`. Delivery uses:
232
243
  }
233
244
  ```
234
245
 
235
- For a terminal run, the visible content contains the workflow identity and revision, terminal run ID, exact stored input, bounded result, terminal state and reason, restart count, and earlier terminal outcomes in the chain. It tells the model to use the current conversation, prefer a safe restart for an unfinished task after a technical or temporary failure, and stop for completed work, cancellation, missing authority, a required user decision, or a repeated failure. Values from input and result are data, not instructions.
246
+ ### Compact TUI card
247
+
248
+ Interactive Pi registers a custom renderer for `pi-workflows-deferred-turn`. The collapsed card shows the workflow name, state or cause, run identity, and restart count when it is available. It does not show the terminal facts JSON, exact input, result, fingerprint, or model instructions.
249
+
250
+ The renderer reads only the structured message details for its compact fields. It sanitizes workflow-derived text and uses Pi's standard TUI components, theme colors, and `expanded` state. A message with missing or invalid details renders as a safe generic workflow card.
251
+
252
+ Expanding the card shows the complete existing message content. The model and session history receive that same content whether the card is collapsed or expanded. Restored messages use the same renderer and do not create another entry or model turn.
253
+
254
+ This display behavior uses the documented `pi.sendMessage()` and `pi.registerMessageRenderer()` APIs. It does not change headless or RPC delivery. Presentation messages that already use `display: false` stay hidden. It adds no Pi core change, private API, database table, migration, store, or external resource.
255
+
256
+ For a terminal run, the complete content contains the workflow identity and revision, terminal run ID, exact stored input, bounded result, terminal state and reason, restart count, and earlier terminal outcomes in the chain. It tells the model to use the current conversation, prefer a safe restart for an unfinished task after a technical or temporary failure, and stop for completed work, cancellation, missing authority, a required user decision, or a repeated failure. Values from input and result are data, not instructions.
236
257
 
237
258
  The content comes only from existing run and queue records. Pi owns conversation history. Pi Workflows does not identify, hash, copy, or store an original user message.
238
259
 
@@ -8,11 +8,25 @@ Pi Workflows stores all live durable state in one database:
8
8
 
9
9
  There is one database for the user installation. Project and run IDs separate data inside it. Workflow targets do not read or write this database.
10
10
 
11
+ ## Viewer projection
12
+
13
+ The database includes the [incremental and virtualized viewer design](plans/2026-08-28-piw-incremental-viewer-plan.md).
14
+
15
+ `viewer_runs` stores one presentation revision and retained revision floor for each run. `viewer_deltas` stores ordered target patches by run, presentation revision, and delta index. `viewer_session_checkpoints` stores the bounded active message and tool state at each 256-event boundary. A viewer-visible transaction writes the domain change, advances the presentation revision, and writes its patch blobs before the same commit. Session-event transactions write each reached replay checkpoint in that transaction.
16
+
17
+ The store retains 256 presentation revisions. A reader with an older cursor must take a bounded snapshot. Patches use `add`, `replace`, `remove`, and `append`. They target small projection documents or pages. Patch creation does not reconstruct and compare complete run views.
18
+
19
+ `session_entries` and `session_events` have run-wide sequence numbers and indexed `(run_id, run_seq)` ranges. Step, trace, entry, and event reads contain at most 256 rows. Run-list queries read metadata, status, lease facts, and the presentation revision. They do not read payload bodies.
20
+
21
+ This is an in-place alpha schema change. The schema name and version remain `pi-workflows-state` version 1. The DDL digest and exact shape changed. An older alpha database fails with the standard reset instruction and remains untouched. There is no compatibility reader, migration shim, dual path, feature flag, alias, or `v2` schema.
22
+
11
23
  ## Storage boundary
12
24
 
13
25
  The database stores:
14
26
 
15
27
  - workflow definitions, runs, events, node attempts, outputs, and updates
28
+ - global host epochs, command receipts, worker epochs, and worker messages
29
+ - durable origin-session interaction requests and submissions
16
30
  - captured Pi session entries and events
17
31
  - run and controller queues, claims, retries, and continuations
18
32
  - human-decision requests, submissions, resolutions, and cancellations
@@ -98,7 +112,8 @@ The shared records do not replace domain schemas. The following `STRICT` tables
98
112
  | ------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
99
113
  | Schema and projects | `schema_meta`, `projects` |
100
114
  | Content | `blobs` |
101
- | Shared lifecycle | `resources`, `leases`, `events` |
115
+ | Shared lifecycle | `resources`, `leases`, `events`, `workflow_host_state` |
116
+ | Host protocol | `host_commands`, `run_workers`, `worker_messages`, `interactive_requests`, `interactive_submissions` |
102
117
  | Workflows | `workflow_definitions`, `runs`, `run_sources`, `run_steps`, `run_bindings`, `run_queue`, `node_attempts`, `workflow_updates` |
103
118
  | Live settings | `workflow_settings`, `workflow_setting_changes` |
104
119
  | Post-run follow-ups | `workflow_follow_up_queues`, `workflow_follow_ups` |
@@ -111,29 +126,23 @@ The shared records do not replace domain schemas. The following `STRICT` tables
111
126
 
112
127
  Foreign keys join projects, runs, attempts, decisions, controllers, effects, and channel records. Partial unique indexes enforce one active node attempt per run, one queued or running reservation per Pi session, one decision winner, and one deterministic effect key. A parked waiting parent does not block its continuation. Reserving that continuation settles the parked parent queue in the same transaction, so a failed reservation leaves the parent recoverable.
113
128
 
114
- ### Terminal restart state
115
-
116
- The [terminal workflow restart contract](plans/2026-08-27-workflow-terminal-restart-plan.md)
117
- uses the existing tables. It adds no state store or table. A restarted run keeps
118
- its root run ID, parent run ID, restart number, and parent terminal fingerprint
119
- in the existing launch-options value. The fingerprint uses the workflow
120
- identity and revision, exact input, terminal state, canonical result or error,
121
- and canonical reason. It excludes run IDs, timestamps, and other values that
122
- change between equivalent attempts.
123
-
124
- One terminal turn intent covers result presentation and factual fallback. A
125
- selected restart, Monitor run, or other workflow start uses the existing
126
- session reservation, run queue, and effect receipt. The successor run's launch
127
- options record the source terminal run, intent, model tool call, and canonical
128
- request fingerprint. A repeated call adopts that reservation or run. Activation
129
- waits for `agent_settled`, and normal queue recovery activates a surviving
130
- reservation once.
131
-
132
- Restart creates a new run. The terminal run remains unchanged. Restart lineage
133
- allows three restarts after the original run and rejects a repeated terminal
134
- fingerprint in the same chain. A Monitor selection records terminal selection
135
- but no restart lineage. Conversation history remains in Pi. This state does not
136
- identify, hash, copy, or store an original user message.
129
+ ### Hosted commands and interactions
130
+
131
+ `workflow_host_state` stores the one current host epoch and its live local
132
+ claim. `host_commands` stores each client request fingerprint, operation,
133
+ outcome, revision, and receipt or error. Repeating an exact request adopts the
134
+ stored receipt. Reusing an ID or idempotency key for another request is a
135
+ conflict.
136
+
137
+ `run_workers` records each worker epoch before spawn and later records its exact
138
+ process identity and terminal outcome. `worker_messages` deduplicates accepted
139
+ state-changing child messages.
140
+
141
+ `interactive_requests` owns durable origin-session work. It stores the run,
142
+ node attempt, target session, contract, presentation claim, accepted
143
+ submission, and revision. `interactive_submissions` stores the idempotency key,
144
+ payload, outcome, and receipt. Pi reload can adopt the saved presentation entry
145
+ without inserting another visible message.
137
146
 
138
147
  ## Content-addressed values
139
148
 
@@ -149,9 +158,9 @@ Readers derive `steps`, `outputs`, `results`, carried-step count, current-node f
149
158
 
150
159
  An agent definition records `expectedOutput` as either a submitted-output description or `{ "kind": "assistant-message", "maxChars"?: number }`. Omitted `maxChars` means that Pi Workflows adds no character limit.
151
160
 
152
- A completed interactive assistant-message attempt uses the settled Pi response entry as its output. It does not store a second output blob. Its small receipt keeps the text digest, final Pi session entry ID, optional author-supplied limit, and whether recovery adopted an existing response. A noninteractive attempt with no captured response entry keeps one normal output blob.
161
+ A completed interactive assistant-message attempt stores the accepted visible text as its node output. Its small receipt keeps the text digest, final Pi session entry ID, optional author-supplied limit, and whether recovery adopted an existing response.
153
162
 
154
- An interrupted assistant-message attempt keeps its attempt ID when the origin Pi session resumes it. The executor adopts a matching completed assistant child from the active Pi branch instead of displaying the response twice. Submitted and non-agent attempts keep their normal fresh-attempt resume behavior.
163
+ An interrupted assistant-message attempt keeps its attempt ID. The extension adopts a matching durable request and existing Pi branch entry instead of displaying the prompt or accepting the response twice. Submitted and non-agent attempts use a fresh execution attempt after an uncommitted worker exit.
155
164
 
156
165
  ## Write contract
157
166
 
@@ -163,6 +172,7 @@ verify the exact schema
163
172
  verify the actor and operation
164
173
  verify the expected resource revision
165
174
  verify the claim token, generation, and expiry when ownership is required
175
+ renew that exact still-live token and generation
166
176
  verify the domain transition
167
177
  write content-addressed values
168
178
  increment the resource revision
@@ -189,7 +199,7 @@ Reading or finding a row never gives write authority.
189
199
  - Control commands have narrow explicit operations, such as requesting cancellation or deletion.
190
200
  - Model-originated workflow answers cannot resolve protected human decisions.
191
201
 
192
- Stores check ownership in the same transaction as the write. Shared scans, status commands, lists, viewers, and the Rust `piw` program are read-only.
202
+ The global host is the normal state writer. Its protected stores check ownership and renew the exact live claim in the same transaction as the write. A stale or expired owner cannot renew itself. Pi extensions and mutating CLI commands use the local host protocol. Shared scans, status commands, lists, viewers, and the Rust `piw` program are read-only.
193
203
 
194
204
  ## Competing outcomes
195
205
 
@@ -209,9 +219,9 @@ Status is a pure projection of domain rows, immutable facts, current leases, and
209
219
 
210
220
  A settings scope uses its resource revision as its public change number. Each accepted patch, current value, and node binding is saved in one transaction. A checkpoint continuation keeps the same settings resources and transfers them to the continuation run.
211
221
 
212
- A follow-up queue records acceptance order and final-presentation state. Successful terminalization changes queued items to ready or pending presentation in the same transaction as the terminal run fact. Failure, timeout, and cancellation cancel unsent items. Delivery uses item leases and active Pi branch evidence.
222
+ A follow-up queue records acceptance order and settlement state. Failure, timeout, and cancellation cancel unsent items.
213
223
 
214
- - A terminal run fact overrides stale queue presentation.
224
+ - A terminal run fact overrides stale delivery state.
215
225
  - An accepted decision is accepted even if its continuation effect is still pending.
216
226
  - A cancelled decision is cancelled even if parent cleanup is still pending.
217
227
  - A stale owner is not shown as current.
@@ -221,7 +231,7 @@ Read paths do not repair state. Owner reconcilers apply pending effects and writ
221
231
 
222
232
  ## Projects and concurrency
223
233
 
224
- All projects use the same file. `projects` stores a stable ID and canonical path. Project-scoped controller and run queries use that key. Standalone host lock and child-process registry files use a project hash under the workflow state directory, so different projects can run hosts concurrently while two hosts for one project still conflict.
234
+ All projects use the same file. `projects` stores a stable ID and canonical path. Project-scoped controller and run queries use that key. One global host owns the file for the user installation. Its socket, lock, and exact child-process registry are under `~/.pi/agent/workflows/host/`. A second live host is rejected even when it was started from another project.
225
235
 
226
236
  SQLite WAL permits concurrent readers while one writer commits. Writers are serialized by SQLite and must keep transactions short. Hashing, model calls, shell work, and external requests happen outside write transactions.
227
237
 
@@ -259,4 +269,4 @@ It does not print actor IDs, channel references, payloads, or credentials.
259
269
 
260
270
  This is a hard cut. Pi Workflows has no normal reader or writer for older live storage. It does not use dual reads, dual writes, aliases, versioned state roots, or automatic import.
261
271
 
262
- Older state remains untouched. If it is present when a new database would be created, Pi Workflows fails with a clear instruction instead of guessing or deleting data.
272
+ Older state remains untouched. Pi Workflows fails before mutation with this instruction: “Pi Workflows durable state is incompatible. Back up and move state.sqlite with its -wal and -shm files, then start Pi Workflows to create a new state.sqlite database. The incompatible state was not changed.”