@osolmaz/pi-workflows 0.16.5 → 0.16.6

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 (171) hide show
  1. package/README.md +5 -5
  2. package/dist/builtins/autoimplement.workflow.d.ts +5 -1
  3. package/dist/builtins/autoimplement.workflow.js +29 -10
  4. package/dist/builtins/autoimplement.workflow.js.map +1 -1
  5. package/dist/client/client.js +3 -1
  6. package/dist/client/client.js.map +1 -1
  7. package/dist/client/protocol.d.ts +1 -1
  8. package/dist/client/protocol.js +18 -1
  9. package/dist/client/protocol.js.map +1 -1
  10. package/dist/client/view.d.ts +7 -5
  11. package/dist/extension/index.d.ts +2 -1
  12. package/dist/extension/index.js +115 -165
  13. package/dist/extension/index.js.map +1 -1
  14. package/dist/extension/recorder.d.ts +1 -1
  15. package/dist/extension/recorder.js +2 -2
  16. package/dist/extension/recorder.js.map +1 -1
  17. package/dist/extension/step-message.d.ts +1 -1
  18. package/dist/extension/step-message.js +4 -4
  19. package/dist/extension/step-message.js.map +1 -1
  20. package/dist/extension/workflow-message-coordinator.d.ts +12 -1
  21. package/dist/extension/workflow-message-coordinator.js +65 -54
  22. package/dist/extension/workflow-message-coordinator.js.map +1 -1
  23. package/dist/resource-managers/index.d.ts +1 -2
  24. package/dist/resource-managers/index.js +1 -2
  25. package/dist/resource-managers/index.js.map +1 -1
  26. package/dist/resource-managers/sqlite.d.ts +2 -295
  27. package/dist/resource-managers/sqlite.js +71 -1528
  28. package/dist/resource-managers/sqlite.js.map +1 -1
  29. package/dist/resource-managers/types.d.ts +2 -7
  30. package/dist/server/resource-runner-entry.js +2 -6
  31. package/dist/server/resource-runner-entry.js.map +1 -1
  32. package/dist/server/rpc-bridge.js +1 -1
  33. package/dist/server/rpc-bridge.js.map +1 -1
  34. package/dist/server/rpc-executor.js +2 -4
  35. package/dist/server/rpc-executor.js.map +1 -1
  36. package/dist/server/server-entry.js +3 -0
  37. package/dist/server/server-entry.js.map +1 -1
  38. package/dist/server/server.d.ts +9 -8
  39. package/dist/server/server.js +364 -541
  40. package/dist/server/server.js.map +1 -1
  41. package/dist/server/state.d.ts +16 -29
  42. package/dist/server/state.js +105 -319
  43. package/dist/server/state.js.map +1 -1
  44. package/dist/server/view.d.ts +4 -5
  45. package/dist/server/view.js +51 -51
  46. package/dist/server/view.js.map +1 -1
  47. package/dist/server/workflow-runner-entry.d.ts +1 -1
  48. package/dist/server/workflow-runner-entry.js +1 -49
  49. package/dist/server/workflow-runner-entry.js.map +1 -1
  50. package/dist/server/workflow-runner-protocol.d.ts +2 -7
  51. package/dist/server/workflow-runner-protocol.js +5 -10
  52. package/dist/server/workflow-runner-protocol.js.map +1 -1
  53. package/dist/server/workflow-runner-store.d.ts +5 -6
  54. package/dist/server/workflow-runner-store.js +7 -14
  55. package/dist/server/workflow-runner-store.js.map +1 -1
  56. package/dist/state/attempt-time.d.ts +21 -0
  57. package/dist/state/attempt-time.js +95 -0
  58. package/dist/state/attempt-time.js.map +1 -0
  59. package/dist/state/json.d.ts +2 -1
  60. package/dist/state/json.js.map +1 -1
  61. package/dist/state/project-store.d.ts +39 -0
  62. package/dist/state/project-store.js +154 -0
  63. package/dist/state/project-store.js.map +1 -0
  64. package/dist/state/prune.js +0 -7
  65. package/dist/state/prune.js.map +1 -1
  66. package/dist/state/schema.js +23 -23
  67. package/dist/state/workflow-messages.d.ts +1 -1
  68. package/dist/state/workflow-messages.js +3 -1
  69. package/dist/state/workflow-messages.js.map +1 -1
  70. package/dist/viewer/backup.js +5 -0
  71. package/dist/viewer/backup.js.map +1 -1
  72. package/dist/workflows/diagnostics.d.ts +8 -0
  73. package/dist/workflows/diagnostics.js +61 -0
  74. package/dist/workflows/diagnostics.js.map +1 -0
  75. package/dist/workflows/engine.d.ts +2 -15
  76. package/dist/workflows/engine.js +125 -245
  77. package/dist/workflows/engine.js.map +1 -1
  78. package/dist/workflows/human-decision.d.ts +3 -7
  79. package/dist/workflows/human-decision.js +2 -56
  80. package/dist/workflows/human-decision.js.map +1 -1
  81. package/dist/workflows/index.d.ts +2 -1
  82. package/dist/workflows/index.js +1 -0
  83. package/dist/workflows/index.js.map +1 -1
  84. package/dist/workflows/queue.d.ts +267 -0
  85. package/dist/workflows/queue.js +1248 -0
  86. package/dist/workflows/queue.js.map +1 -0
  87. package/dist/workflows/requests.d.ts +47 -0
  88. package/dist/workflows/requests.js +158 -0
  89. package/dist/workflows/requests.js.map +1 -0
  90. package/dist/workflows/schema.js +2 -4
  91. package/dist/workflows/schema.js.map +1 -1
  92. package/dist/workflows/store.d.ts +11 -16
  93. package/dist/workflows/store.js +174 -169
  94. package/dist/workflows/store.js.map +1 -1
  95. package/dist/workflows/tool-input.d.ts +6 -9
  96. package/dist/workflows/tool-input.js +15 -12
  97. package/dist/workflows/tool-input.js.map +1 -1
  98. package/dist/workflows/transitions.d.ts +38 -0
  99. package/dist/workflows/transitions.js +276 -0
  100. package/dist/workflows/transitions.js.map +1 -0
  101. package/dist/workflows/types.d.ts +9 -42
  102. package/dist/workflows/workflow-message-content.d.ts +7 -1
  103. package/dist/workflows/workflow-message-content.js +22 -4
  104. package/dist/workflows/workflow-message-content.js.map +1 -1
  105. package/docs/2026-08-25-workflow-follow-ups.md +5 -5
  106. package/docs/2026-09-02-unify-workflow-messages-plan.md +2 -0
  107. package/docs/2026-09-04-workflow-run-state-plan.md +2 -0
  108. package/docs/2026-09-06-durable-execution-plan.md +176 -0
  109. package/docs/DEFERRED_TURNS.md +84 -105
  110. package/docs/HUMAN_DECISIONS.md +17 -19
  111. package/docs/HUMAN_DECISION_PRESENTATIONS.md +1 -1
  112. package/docs/SQLITE_STATE.md +68 -29
  113. package/docs/WORKFLOW_SERVER.md +21 -10
  114. package/docs/WORKFLOW_STEP_MESSAGES.md +72 -54
  115. package/docs/plans/2026-08-04-controller-runtime-plan.md +2 -0
  116. package/docs/plans/2026-08-21-deferred-turn-intents-plan.md +2 -0
  117. package/docs/plans/2026-08-27-workflow-terminal-restart-plan.md +2 -0
  118. package/docs/workflows.md +94 -45
  119. package/examples/workflows/autoresearch.workflow.ts +0 -2
  120. package/examples/workflows/branch.workflow.ts +0 -4
  121. package/examples/workflows/echo.workflow.ts +0 -2
  122. package/examples/workflows/live-settings.workflow.ts +0 -2
  123. package/examples/workflows/two-turn.workflow.ts +0 -2
  124. package/herdr-plugin.toml +1 -1
  125. package/package.json +1 -1
  126. package/protocol/fixtures/client-v1.json +1 -1
  127. package/skills/pi-workflows/SKILL.md +8 -7
  128. package/src/builtins/autoimplement.workflow.ts +31 -10
  129. package/src/client/client.ts +3 -1
  130. package/src/client/protocol.ts +21 -1
  131. package/src/client/view.ts +9 -5
  132. package/src/extension/index.ts +117 -189
  133. package/src/extension/recorder.ts +2 -2
  134. package/src/extension/step-message.ts +4 -4
  135. package/src/extension/workflow-message-coordinator.ts +78 -50
  136. package/src/resource-managers/index.ts +1 -9
  137. package/src/resource-managers/sqlite.ts +330 -2655
  138. package/src/resource-managers/types.ts +2 -3
  139. package/src/server/resource-runner-entry.ts +2 -7
  140. package/src/server/rpc-bridge.ts +1 -1
  141. package/src/server/rpc-executor.ts +4 -9
  142. package/src/server/server-entry.ts +3 -0
  143. package/src/server/server.ts +416 -641
  144. package/src/server/state.ts +122 -466
  145. package/src/server/view.ts +56 -60
  146. package/src/server/workflow-runner-entry.ts +2 -55
  147. package/src/server/workflow-runner-protocol.ts +5 -15
  148. package/src/server/workflow-runner-store.ts +12 -27
  149. package/src/state/attempt-time.ts +122 -0
  150. package/src/state/json.ts +2 -1
  151. package/src/state/project-store.ts +218 -0
  152. package/src/state/prune.ts +0 -13
  153. package/src/state/schema.ts +23 -23
  154. package/src/state/workflow-messages.ts +4 -2
  155. package/src/viewer/backup.ts +7 -0
  156. package/src/workflows/diagnostics.ts +78 -0
  157. package/src/workflows/engine.ts +133 -291
  158. package/src/workflows/human-decision.ts +3 -81
  159. package/src/workflows/index.ts +8 -2
  160. package/src/workflows/queue.ts +2091 -0
  161. package/src/workflows/requests.ts +283 -0
  162. package/src/workflows/schema.ts +2 -6
  163. package/src/workflows/store.ts +223 -231
  164. package/src/workflows/tool-input.ts +20 -12
  165. package/src/workflows/transitions.ts +330 -0
  166. package/src/workflows/types.ts +9 -46
  167. package/src/workflows/workflow-message-content.ts +28 -4
  168. package/dist/resource-managers/runtime.d.ts +0 -59
  169. package/dist/resource-managers/runtime.js +0 -423
  170. package/dist/resource-managers/runtime.js.map +0 -1
  171. package/src/resource-managers/runtime.ts +0 -576
@@ -18,7 +18,7 @@ A workflow author can:
18
18
  - collect exact text with a choice such as `replan`;
19
19
  - address a logical audience instead of a Telegram chat or Pi session;
20
20
  - reuse a standard plan approval workflow; and
21
- - rely on one accepted answer and one continuation after a crash or concurrent reply.
21
+ - rely on one accepted answer and one resumed attempt after a crash or concurrent reply.
22
22
 
23
23
  The feature uses the existing checkpoint execution primitive. It does not add another engine node type.
24
24
 
@@ -124,7 +124,7 @@ When the engine reaches the node, it:
124
124
  4. asks the configured channels to deliver it; and
125
125
  5. parks the run in `waiting` state.
126
126
 
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.
127
+ A valid answer completes the exact checkpoint attempt in the same run. The original workflow input stays unchanged. The answer becomes that node's output. Ordinary checkpoints also complete in place, but use the normal `answer` action. A protected decision accepts only the verified human path or its declared timeout policy.
128
128
 
129
129
  ## Request and response contracts
130
130
 
@@ -265,15 +265,15 @@ Human decisions use the canonical [SQLite state](SQLITE_STATE.md) database:
265
265
  - `human_decisions` stores each immutable request;
266
266
  - `human_decision_submissions` records human, policy, channel, and control candidates;
267
267
  - `human_decision_resolutions` stores the one accepted-or-cancelled winner;
268
- - `continuations` links the parent and continuation runs;
269
- - `effects` and `effect_attempts` record parent settlement, continuation, external delivery, and external settlement work; and
268
+ - `interactive_requests` and `interactive_submissions` bind the accepted resolution to the exact node attempt;
269
+ - `effects` and `effect_attempts` record run settlement, external delivery, and external settlement work; and
270
270
  - `channels`, `channel_cursors`, and `channel_messages` store channel identity, polling position, and decision delivery or settlement receipts.
271
271
 
272
272
  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.
273
273
 
274
- 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
+ A deadline with a validated default response is timeout-policy acceptance. It cannot become expiry cancellation. No-default expiry can cancel. Automatic policy and execution require the current owner's token and lease generation. A verified channel can submit a human candidate without gaining run ownership.
275
275
 
276
- 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
+ The scheduler resumes the same run and exact request. Accepted resolution and redacted receipt records carry the 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.
277
277
 
278
278
  ## Planning workflow composition
279
279
 
@@ -312,7 +312,7 @@ Recovery follows these rules:
312
312
  - ambiguous Telegram sends are not retried automatically;
313
313
  - duplicate channel updates are harmless;
314
314
  - stale responses are rejected;
315
- - one human or timeout response creates one continuation;
315
+ - one human or timeout response completes one exact checkpoint attempt;
316
316
  - the winning human answer or timeout policy settles or cancels the pending Pi decision workflow message;
317
317
  - confirmed channel settlement is adopted without another remote call;
318
318
  - failed channel settlement has a bounded retry count and cannot create an unbounded record loop; and
@@ -320,20 +320,18 @@ Recovery follows these rules:
320
320
 
321
321
  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.
322
322
 
323
- Direct answer handling and recovery can notice the same accepted decision at the same time. Both paths use one continuation coordinator. The coordinator prepares the deterministic continuation through the existing durable run queue. The first caller creates and claims the queue row. Only that caller receives the claim token and starts the engine. A compatible later caller adopts the existing row without starting another engine.
324
-
325
- Adoption does not change the lease, claim generation, queue state, timestamps, or events. An adopter cannot renew, release, park, complete, or replace the winning claim. Reuse with a different workflow source, definition, input, launch options, parent, or owning session fails without changing the existing run. If the winning process stops after it claims the row, existing lease expiry and activation recovery can continue the prepared run.
323
+ Direct answer handling and recovery can observe the same accepted decision. Both use the exact saved request and the common scheduler. Response acceptance makes the original run eligible; it does not create or directly launch another run. One lease generation owns execution. An identical response retry adopts its receipt, while a conflicting payload or wrong request kind is rejected before queue changes. Source verification and generation fencing remain required after interruption.
326
324
 
327
325
  ## Compatibility
328
326
 
329
- 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. The continuation startup fix uses existing queue and lease records. It adds no field, table, migration, or schema version. Existing compatible prepared or initialized continuations are adopted. 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.
327
+ This alpha contract changes in place. Incompatible state remains untouched and fails with a backup-and-reset instruction. There is no compatibility reader, migration, dual path, or new schema generation. Viewers show the decision deadline and declared automatic action, while keeping the canonical subject separate. Private channel configuration and transport identifiers remain hidden.
330
328
 
331
- The engine remains independent from Pi and Telegram. Core code owns decision contracts and validation. The server owns durable acceptance, continuation, workflow messages, and channel-child supervision. The Pi extension owns documented session presentation and controls. The Telegram adapter child owns Bot API translation. Workflow definitions own only the question, choices, audience, and routes.
329
+ The engine remains independent from Pi and Telegram. Core code owns decision contracts and validation. The server owns durable acceptance, scheduling, workflow messages, and channel-child supervision. The Pi extension owns documented session presentation and controls. The Telegram adapter child owns Bot API translation. Workflow definitions own only the question, choices, audience, and routes.
332
330
 
333
331
  ## Contract impact
334
332
 
335
333
  - **Session state:** Pi records normal workflow messages and interactive decision results.
336
- - **Other persistent data:** decision requests and resolutions can carry a deadline, automatic response, and resolution provenance in the existing decision store. Continuation startup uses existing run, queue, source, binding, lease, event, continuation, and decision-effect records. External channel attempts use the shared effect records, and channel delivery and settlement records remain separate feature evidence.
334
+ - **Other persistent data:** decision requests and resolutions can carry a deadline, automatic response, and resolution provenance in the existing decision store. Same-run resumption uses the existing request, queue, source, binding, lease, event, and effect records. External channel attempts use the shared effect records, and channel delivery and settlement records remain separate feature evidence.
337
335
  - **Pi internals:** none.
338
336
  - **Public Pi API:** documented extension lifecycle, message, command, session, and status APIs only.
339
337
  - **Public pi-workflows API:** typed human choices, `humanDecision().onTimeout`, `humanDecisionEdge()`, channel profile configuration, the plan approval policy, the shared plan-change workflow, and the additive queue prepare-or-adopt operation.
@@ -345,7 +343,7 @@ The implementation must test:
345
343
  - compile-time exhaustive choice routing;
346
344
  - choices with no input and choices with exact text input;
347
345
  - runtime choice and input validation;
348
- - legacy checkpoint continuation;
346
+ - ordinary and protected checkpoints completing in the original run;
349
347
  - model-tool answer rejection;
350
348
  - Pi decision workflow messages, branch adoption, branch-specific decision messages, and verified answers;
351
349
  - supervised channel-child protocol fencing and restart recovery;
@@ -354,14 +352,14 @@ The implementation must test:
354
352
  - stale request digests;
355
353
  - concurrent Pi and Telegram answers;
356
354
  - a direct verified answer racing recovery for the same accepted decision;
357
- - one claim generation, one engine start, one continuation, and one execution of each continuation node;
358
- - compatible continuation adoption without lease, queue, timestamp, or event mutation;
359
- - incompatible continuation identity reuse without mutation;
355
+ - one authoritative claim generation and no extra run after a decision;
356
+ - accepted-response adoption without new model work or copied state;
357
+ - request identity and response-kind mismatches rejected before domain mutation;
360
358
  - identical and conflicting retries;
361
- - crashes before and after answer acceptance and continuation creation;
359
+ - crashes before and after answer acceptance and same-run resumption;
362
360
  - ambiguous Telegram sends;
363
361
  - one supervised long-poll adapter per profile and server-restart recovery;
364
362
  - decision cancellation and expiry;
365
363
  - included `plan-approval` routes and bounded replan loops;
366
364
  - viewer redaction; and
367
- - real Pi execution without real Telegram credentials or network calls, duplicate-start failures, revision conflicts, or a stranded running continuation.
365
+ - real Pi execution without real Telegram credentials or network calls, duplicate-start failures, revision conflicts, or a stranded resumed run.
@@ -279,7 +279,7 @@ presentation. Renderers treat all text as untrusted and inert.
279
279
  - Ambiguous Telegram delivery remains unknown and is not retried blindly.
280
280
  - A channel failure does not choose a default answer.
281
281
  - Rules for the first valid answer and stale answers remain unchanged, as do
282
- cancellation and settlement rules together with exactly-once continuation.
282
+ cancellation and settlement rules together with exact same-run response adoption.
283
283
 
284
284
  ## Verification
285
285
 
@@ -16,6 +16,8 @@ The database includes the [incremental and virtualized viewer design](plans/2026
16
16
 
17
17
  `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. `run_view_content` stores generated reference bytes under the exact run ID, content digest, and media type. It is separate from general state blobs, and content reads require all three identities. 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.
18
18
 
19
+ Run views expose the presentation cursor as `revision` and the execution resource revision as `runRevision`. Commands such as restart use `runRevision`. Conversation capture can advance the presentation cursor without changing execution, so the two values must not be substituted for each other.
20
+
19
21
  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.
20
22
 
21
23
  `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.
@@ -30,7 +32,7 @@ The database stores:
30
32
  - global server epochs, command receipts, runner epochs, and runner messages
31
33
  - durable origin-session interaction requests and submissions
32
34
  - captured Pi session entries and events
33
- - run and resource manager queues, claims, retries, and continuations
35
+ - run and resource manager queues, claims, and declared retries
34
36
  - human-decision requests, submissions, resolutions, and cancellations
35
37
  - managed resources, finalizers, effects, and child workflows
36
38
  - workflow messages that Pi must add to origin conversations
@@ -112,31 +114,31 @@ Telegram delivery and settlement use these shared effect records. The server rec
112
114
 
113
115
  The shared records do not replace domain schemas. The following `STRICT` tables keep the state explicit:
114
116
 
115
- | Area | Tables |
116
- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
117
- | Schema and projects | `schema_meta`, `projects` |
118
- | Content | `blobs`, `run_view_content` |
119
- | Shared lifecycle | `resources`, `leases`, `events`, `workflow_host_state` |
120
- | Server protocol | `host_commands`, `run_workers`, `worker_messages`, `interactive_requests`, `interactive_submissions` |
121
- | Workflows | `workflow_definitions`, `runs`, `run_sources`, `run_steps`, `run_bindings`, `run_queue`, `node_attempts`, `workflow_updates` |
122
- | Live settings | `workflow_settings`, `workflow_setting_changes` |
123
- | Post-run follow-ups | `workflow_follow_ups` |
124
- | Session capture | `session_segments`, `session_entries`, `attempt_entries`, `session_events` |
125
- | Human decisions | `human_decisions`, `human_decision_resolutions`, `human_decision_submissions`, `continuations` |
126
- | Managed resources | `controller_resources`, `controller_finalizers`, `controller_queue`, `controller_workflows` |
127
- | Effects | `effects`, `effect_attempts` |
128
- | Pi messages | `workflow_messages`, `workflow_turns` |
129
- | Channels | `channels`, `channel_cursors`, `channel_messages` |
117
+ | Area | Tables |
118
+ | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
119
+ | Schema and projects | `schema_meta`, `projects` |
120
+ | Content | `blobs`, `run_view_content` |
121
+ | Shared lifecycle | `resources`, `leases`, `events`, `workflow_host_state` |
122
+ | Server protocol | `host_commands`, `run_workers`, `worker_messages`, `interactive_requests`, `interactive_submissions` |
123
+ | Workflows | `workflow_definitions`, `runs`, `run_sources`, `run_steps`, `run_bindings`, `run_queue`, `node_attempts`, `attempt_active_intervals`, `workflow_updates` |
124
+ | Live settings | `workflow_settings`, `workflow_setting_changes` |
125
+ | Post-run follow-ups | `workflow_follow_ups` |
126
+ | Session capture | `session_segments`, `session_entries`, `attempt_entries`, `session_events` |
127
+ | Human decisions | `human_decisions`, `human_decision_resolutions`, `human_decision_submissions` |
128
+ | Managed resources | `controller_resources`, `controller_finalizers`, `controller_queue`, `controller_workflows` |
129
+ | Effects | `effects`, `effect_attempts` |
130
+ | Pi messages | `workflow_messages`, `workflow_turns` |
131
+ | Channels | `channels`, `channel_cursors`, `channel_messages` |
130
132
 
131
133
  The `host_*`, `run_workers`, `worker_*`, and `controller_*` names remain version-1 internal SQLite identifiers. The `host` and `controller` actor and owner values and the `~/.pi/agent/workflows/host/` state directory also remain internal identifiers. Public APIs and documentation call these components the workflow server, workflow runner, resource manager, resource runner, and managed resource. The alpha hard cut adds no alias or second storage path.
132
134
 
133
- `workflow_messages` is the only table that owns adding workflow content to Pi. It stores the target session, message kind, source record, content digest, session order, `pending`, `sent`, or `cancelled` state, confirmed Pi entry ID, and creation and update times. The table stores no sender, send lease, `sending` state, or separate sent time. Active-branch evidence changes `pending` or `cancelled` to `sent`. Initial, reminder, and resumed prompts are all `step` messages; their custom details contain the reason. Interactive requests, decisions, terminal runs, notifications, follow-ups, and settings keep their own domain state.
135
+ `workflow_messages` is the only table that owns adding workflow content to Pi. It stores the target session, message kind, source record, content digest, session order, `pending`, `sent`, or `cancelled` state, confirmed Pi entry ID, and creation and update times. The table stores no sender, send lease, `sending` state, or separate sent time. Active-branch evidence changes `pending` or `cancelled` to `sent`. Initial and resumed prompts are all `step` messages; their custom details contain the reason. Interactive requests, decisions, terminal runs, notifications, follow-ups, and settings keep their own domain state.
134
136
 
135
137
  `workflow_turns` stores the server-approved ownership of one Pi model turn. Each row names the exact workflow message, run, session, and turn ID. A partial unique index permits only one open turn for a message. The server checks for an exact saved turn or another open turn before insertion, so a normal conflict returns a controlled protocol error instead of a raw SQLite error. Terminalization ends every open turn for that run as `lost` in the same transaction. It cancels pending step and decision messages, plus follow-ups when the run did not complete successfully. Committed notifications remain eligible. Matching late reports adopt the saved result, while conflicting identities remain errors.
136
138
 
137
139
  `channels` stores configured channel resource identities. `channel_cursors` stores the last accepted external polling position. `channel_messages` stores immutable decision delivery and settlement records for audit and duplicate evidence. External application state and Telegram message references belong to `effects` and `effect_attempts`.
138
140
 
139
- Foreign keys join projects, runs, attempts, decisions, managed resources, effects, and channel records. Partial unique indexes enforce one active node attempt per run, one pending step message per interaction request, one nonterminal interactive continuation-chain reservation per Pi session, one decision winner, and one deterministic effect key. A run waiting for a checkpoint or protected decision keeps that chain reservation. A parked waiting parent does not block its own continuation. Reserving that continuation transfers the reservation and settles the parked parent queue in the same transaction, so a failed reservation leaves the parent recoverable.
141
+ Foreign keys join projects, runs, attempts, decisions, managed resources, effects, and channel records. Partial unique indexes enforce one active node attempt per run, one pending step message per request, one nonterminal interactive run per Pi session, one decision winner, and one deterministic effect key. A waiting or paused run keeps its session reservation. Checkpoint responses complete nodes in that same run; they do not reserve child runs.
140
142
 
141
143
  ### ServerBacked commands and interactions
142
144
 
@@ -150,7 +152,7 @@ conflict.
150
152
  process identity and terminal outcome. `worker_messages` deduplicates accepted
151
153
  state-changing child messages.
152
154
 
153
- `interactive_requests` owns the durable request contract and workflow state for origin-session work. It stores the run, node attempt, target session, contract, request status, accepted submission, `unproductiveTurnEnds`, and revision. Pause is stored once on the run and derived for its one pending interaction. `interactive_submissions` stores the idempotency key, payload, outcome, and receipt. It stores no Pi presentation claim or Pi session entry. `workflow_messages` owns those facts, and the extension reports the active Pi branch after reload.
155
+ `interactive_requests` owns the durable request contract and workflow state for origin-session work. It stores the run, node attempt, target session, contract, request status, accepted submission, and revision. Pause is stored once on the run and derived for its one pending interaction. `interactive_submissions` stores the idempotency key, payload, outcome, and receipt. It stores no Pi presentation claim or Pi session entry. `workflow_messages` owns those facts, and the extension reports the active Pi branch after reload.
154
156
 
155
157
  ## Content-addressed values
156
158
 
@@ -158,19 +160,56 @@ state-changing child messages.
158
160
 
159
161
  Insertion verifies the digest, media type, byte length, and exact bytes. Repeated content adopts the existing row. This replaces separate artifact files while keeping outputs, errors, settled Pi entries, and rendered channel text deduplicated. Automatic retention and explicit prune remove unreferenced blobs after they delete safe old run trees. They retain each blob referenced by a database foreign key or active runner transfer.
160
162
 
161
- Runs do not store a nested `WorkflowRunState` blob. `runs` stores run-level facts and hashes for independent values. `run_sources` stores source identity without source JSON blobs. `node_attempts` stores structured workflow outputs and small execution receipts. `session_entries` is the only stored copy of each settled Pi entry. `attempt_entries` links an attempt to its prompt, response, first, and last Pi entries. `run_steps` stores ordered attempt membership and only stores an output override when a continuation changes a carried checkpoint answer.
163
+ Runs do not store a nested `WorkflowRunState` blob. `runs` stores run-level facts and hashes for independent values. `run_sources` stores source identity without source JSON blobs. `node_attempts` stores structured workflow outputs and small execution receipts. `session_entries` is the only stored copy of each settled Pi entry. `attempt_entries` links an attempt to its prompt, response, first, and last Pi entries. `run_steps` stores ordered attempt membership. Each completed checkpoint keeps its own accepted output.
162
164
 
163
- Readers derive `steps`, `outputs`, `results`, carried-step count, current-node fields, waiting state, source objects, and continuation decision receipts from these rows. Compact trace events do not copy prompts, node outputs, run inputs, final outputs, action receipts, or assistant receipts.
165
+ Readers derive `steps`, `outputs`, `results`, current-node fields, waiting state, source objects, and accepted decision receipts from these rows. Compact trace events do not copy prompts, node outputs, run inputs, final outputs, action receipts, or assistant receipts.
164
166
 
165
167
  The run store reads each independent value through its declared media type. Input and final output use JSON readers. Run errors and presentation instructions use text readers. Terminal-message construction uses one typed terminal-data result instead of guessing the blob type. A missing or wrong media type fails presentation after the terminal state commits; it cannot roll back that state.
166
168
 
169
+ ### State verification
170
+
171
+ `state status` reports invariant violations with the affected run IDs. `state verify`
172
+ and inactive-backup verification check the same facts after SQLite integrity and
173
+ foreign-key checks. They detect terminal queue mismatches, waiting runs without
174
+ responses, pending requests on inactive attempts, missing accepted submissions,
175
+ invalid active clocks, and conflicting Pi turns. Verification does not repair
176
+ state or create replacement work.
177
+
178
+ Queued, starting, running, and parked queue phases can coexist with live execution
179
+ states during admission and response validation. They are not contradictions by
180
+ themselves. A pending settlement effect also explains a terminal result whose queue
181
+ has not yet settled. Expired claims remain recovery facts, not proof of completed
182
+ work.
183
+
184
+ ### Active execution time
185
+
186
+ `node_attempts.started_at` is historical data and never changes. `timeout_ms`
187
+ stores the resolved active-time budget: SQL `NULL` means not yet configured,
188
+ zero means explicitly unlimited, and a positive value is the budget in milliseconds.
189
+ The public contract uses `null` for an unlimited timeout.
190
+
191
+ `attempt_active_intervals` records numbered active intervals in the same database.
192
+ Only one interval can be open for an attempt. Each interval keeps its start,
193
+ last observation, end, and measured elapsed time. The host measures duration with
194
+ a monotonic clock and saves samples during normal polling. Wall-clock changes
195
+ cannot spend or restore the active budget. Pause, disconnect, and settlement close
196
+ the interval. Reconnect opens another interval only for an unpaused, active model
197
+ turn. Duplicate starts and overlapping inactive causes cannot count time twice.
198
+
199
+ After a crash, recovery closes an open interval at its last durable observation.
200
+ It does not charge the unobserved gap or server downtime. At most the time since
201
+ the last sample is uncounted. Human decision expiry remains an absolute wall-clock
202
+ deadline. Step start and finish timestamps remain wall-clock history, separate
203
+ from the active-time budget. Interval rows follow their attempt through existing
204
+ foreign-key retention and backup rules.
205
+
167
206
  ### Assistant-message attempts
168
207
 
169
208
  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.
170
209
 
171
210
  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.
172
211
 
173
- 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 runner exit.
212
+ 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. A submitted agent request also retains its exact attempt while waiting, validating, or recovering an accepted receipt. A declared execution retry creates a new attempt; transport recovery does not.
174
213
 
175
214
  ## Write contract
176
215
 
@@ -202,7 +241,7 @@ A TypeScript write permit carries the expected facts between layers. It is not a
202
241
 
203
242
  Reading or finding a row never gives write authority.
204
243
 
205
- - A run owner may advance the run, apply automatic decision policy, create its continuation, settle its parent, and complete its queue work.
244
+ - A run owner may advance the exact run, apply its declared automatic decision policy, and settle its queue work.
206
245
  - A resource manager claim owner may update resource manager status, reserve effects, and start child workflows for that resource.
207
246
  - A verified human channel actor may submit one answer candidate for the named decision. It does not gain run ownership.
208
247
  - The server-owned channel adapter path may update only its channel cursor, decision delivery and settlement records, and exact managed effects.
@@ -221,18 +260,18 @@ A deadline with a validated default response is timeout-policy acceptance. It ca
221
260
 
222
261
  Late or repeated commands return or adopt the durable winner. They do not overwrite it.
223
262
 
224
- The same rule applies to run terminal outcomes, continuation admission, queue settlement, resource manager effects, retry scheduling, channel settlement, and workflow-turn reports through their domain constraints and expected revisions. A matching turn report adopts the saved ownership result. A different report for the same turn ID remains a conflict.
263
+ The same rule applies to run terminal outcomes, exact-request acceptance, queue settlement, resource manager effects, retry scheduling, channel settlement, and workflow-turn reports through their domain constraints and expected revisions. A matching turn report adopts the saved ownership result. A different report for the same turn ID remains a conflict.
225
264
 
226
265
  ## Read contract
227
266
 
228
267
  Durable status is a pure projection of domain rows, immutable facts, current leases, effect results, and exact workflow-turn start and end reports. The server uses these facts to produce one live run view. An open workflow turn can change display status only. It cannot change workflow authority. Every renderer consumes the server-produced display status and allowed controls without running another status reducer.
229
268
 
230
- 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.
269
+ 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 resumes in the same run and keeps the same settings resources.
231
270
 
232
- `workflow_follow_ups` records source acceptance order, removal, and cancellation. The source and message stay attached to the continuation-chain member that accepted them; rows are not rewritten when the chain continues. The server walks the chain to find its final outcome. `workflow_messages` owns message state and Pi entry evidence. Failure, timeout, and cancellation cancel unsent follow-up messages.
271
+ `workflow_follow_ups` records source acceptance order, removal, and cancellation. The source and message stay attached to the run that accepted them. The server uses that run's outcome; an explicit restart does not retarget the prompt. `workflow_messages` owns message state and Pi entry evidence. Failure, timeout, and cancellation cancel unsent follow-up messages.
233
272
 
234
273
  - A terminal run fact overrides stale message state and has no open workflow turn.
235
- - An accepted decision is accepted even if its continuation effect is still pending.
274
+ - An accepted decision stays accepted while the scheduler waits for execution capacity.
236
275
  - A cancelled decision is cancelled even if parent cleanup is still pending.
237
276
  - A stale owner is not shown as current.
238
277
  - An ambiguous external effect is shown as unresolved.
@@ -249,9 +288,9 @@ This contract is for local storage on one machine. It does not claim distributed
249
288
 
250
289
  ## Automatic retention
251
290
 
252
- The server keeps terminal root-run trees for 30 days from `finished_at`. A tree is eligible only when every restart or continuation descendant is terminal, older than the cutoff, free of protected work, and free of references from outside the tree.
291
+ The server keeps terminal root-run trees for 30 days from `finished_at`. A tree is eligible only when every restart descendant is terminal, older than the cutoff, free of protected work, and free of references from outside the tree.
253
292
 
254
- Automatic cleanup keeps a tree when it has a waiting or parked run, a live queue row, a pending workflow message, an open workflow turn, a pending interaction or human decision, a recording session segment, a queued follow-up, an active lease, an unsettled effect, controller ownership, an active runner content hash, a resumable checkpoint, an undelivered terminal result, or a continuation or step reference from outside the tree. Unknown or conflicting ownership also blocks deletion.
293
+ Automatic cleanup keeps a tree when it has a waiting or parked run, a live queue row, a pending workflow message, an open workflow turn, a pending interaction or human decision, a recording session segment, a queued follow-up, an active lease, an unsettled effect, controller ownership, an active runner content hash, a resumable checkpoint, an undelivered terminal result, or a run or step reference from outside the tree. Unknown or conflicting ownership also blocks deletion.
255
294
 
256
295
  The server requests cleanup after startup recovery and after workflow runners exit. It also schedules the next daily check after a completed sweep. Overlapping requests use one in-process task. Cleanup starts only while there is no active or pending workflow runner, resource-manager runner, state-maintenance command, or shutdown. One server process completes no more than one sweep in 24 hours. A due sweep that finds work active or stops between trees remains due. The next idle lifecycle trigger or a five-minute idle retry continues it.
257
296
 
@@ -115,6 +115,16 @@ Claim rejection uses `ClaimLostError` with one internal reason:
115
115
 
116
116
  Logs may show the run ID, generation, and reason. They must not show a raw token or token hash.
117
117
 
118
+ ## Worker scheduling
119
+
120
+ `WorkflowRunQueueStore` in `src/workflows/queue.ts` owns run reservations, queue state, and execution claims. `SqliteResourceManagerStore` owns managed resources and reconcile state, not workflow scheduling. The host is the only reconciliation runtime; the separate `ResourceManagerRuntime` API has been removed. Reconcile results and failure requeues commit atomically, and a worker that loses its claim cannot write a failure or start a retry. They share the existing SQLite connection and revision helpers; neither introduces another database.
121
+
122
+ One scheduler admits workflow runners and resource-manager reconciles. Start and restart reserve a queued run; resume makes existing work eligible. Accepted responses, validation candidates, and expired interactive deadlines use the same admission path. Requests remain durable while capacity is full.
123
+
124
+ The default capacity is four execution workers. Set `PI_WORKFLOWS_MAX_WORKERS` to a positive integer before starting the host to change it. Embedded test hosts can use `WorkflowServer({ maxWorkers })`. Capacity includes pending launches and live workers. The scheduler alternates workflow and resource-manager admissions when both have work. Claim renewal, cancellation, and cleanup do not wait for an execution slot.
125
+
126
+ An interactive run holds its origin-session reservation while queued, running, waiting, or paused. Independent headless runs do not reserve a Pi session. Only terminal completion or explicit cancellation releases an interactive reservation.
127
+
118
128
  ## Run lifecycle
119
129
 
120
130
  The run and queue projections follow these states:
@@ -259,15 +269,16 @@ Each history page has both an item limit and an encoded byte budget. Oversized v
259
269
 
260
270
  The closed `display.status` set is `queued`, `running`, `waiting`, `paused`, `completed`, `failed`, `timed_out`, `cancelled`, and `ambiguous`.
261
271
 
262
- The server computes effective status in this order:
272
+ The server keeps execution status separate from worker and Pi activity:
273
+
274
+ 1. A durable terminal result keeps its terminal label, including during reporting or cleanup. An unresolved external effect still exposes a review action.
275
+ 2. A nonterminal run with an ambiguous external effect is `ambiguous`.
276
+ 3. A durable pause is `paused`, even while its worker or Pi turn stops.
277
+ 4. A pending request is `waiting`. Its kind determines the response action: `submit` and `update` for an agent, `answer` for an ordinary checkpoint, `human-answer` for a protected decision, and no tool completion action for a visible-response request.
278
+ 5. Otherwise, active worker or Pi execution is `running`.
279
+ 6. Parked resumable work or admitted work that has not started is `queued`.
263
280
 
264
- 1. A durable ambiguous external effect that requires explicit review is `ambiguous`. An effect that is still applying under a live runner is not ambiguous.
265
- 2. A live supervised runner or an exact active origin-session workflow turn is `running`.
266
- 3. A durable terminal result keeps its terminal label after its presentation turn ends.
267
- 4. A durable pause is `paused` after its active Pi turn ends.
268
- 5. A pending interaction, decision, or presentation with no exact active turn is `waiting`.
269
- 6. Parked resumable work with no pending interaction is `queued`.
270
- 7. Admitted work that has not started is `queued`.
281
+ `display.activity` reports the supervised worker or exact Pi turn independently. Waiting alone never permits an answer. Command handlers check the exact request again before they change state.
271
282
 
272
283
  Server connection failure is the client condition `unavailable`, not a `display.status` value. `paused` is never inferred from a parked queue, pending interaction, stale cursor, or missing activity report.
273
284
 
@@ -383,7 +394,7 @@ Active-branch absence is usable only when the branch has no matching ID, Pi is i
383
394
 
384
395
  The extension subscribes to the active origin-session live run view and projects it into Pi's documented widget and status APIs. It never opens SQLite, runs workflow code, or derives a display status. `Shift+Up` and `Shift+Down` scroll the widget. When Herdr is available, the widget also shows `Ctrl+Shift+R piw`, and `/piw` remains the command fallback. Both actions open or focus the exact run from the same view.
385
396
 
386
- A tool update or submission goes to the server. It includes the exact request, node, attempt, expected revision, and tool-call idempotency key. The server first checks this transport contract and records a provisional `validating` submission. It then schedules a supervised workflow child. Only that child loads workflow code and runs the node's `validate` function. The child reports `interaction.accepted` or `interaction.rejected` to the server. The server settles the request only after acceptance. A rejected payload leaves the same request pending and returns the stored actionable error to the model. If the child stops before it reports a result, the server rejects the provisional submission and leaves the request ready for a corrected retry.
397
+ A tool update or submission goes to the server with an exact `requestId`, the tool-call idempotency key, and the current origin-session coordinator authority. The server resolves the immutable node and attempt from that request. It checks session ownership and request kind before receipt lookup or mutation. An identical submission can adopt its saved result after settlement. A new submission records a provisional `validating` candidate. It then schedules a supervised workflow child. Only that child loads workflow code and runs the node's `validate` function. The child reports `interaction.accepted` or `interaction.rejected` to the server. The server settles the request only after acceptance. A rejected payload leaves the same request pending and returns the stored actionable error to the model. If the child stops before it reports a result, the server rejects the provisional submission and leaves the request ready for a corrected retry.
387
398
 
388
399
  An ordinary checkpoint accepts the model-facing `answer` action and starts a continuation run. A protected human decision never accepts that tool action. The extension displays the decision without starting a model turn, and a person answers it with `/workflow answer` through `decision.answer`. When a protected decision reaches its saved `onTimeout` deadline, the server takes a control claim on the waiting parent, atomically records the validated default, closes the pending interaction, releases the parent claim, and reserves the continuation. A human answer cannot win after that deadline.
389
400
 
@@ -397,7 +408,7 @@ A notify node creates a passive `notification` message in the same transaction a
397
408
 
398
409
  A run with headless execution mode uses the existing `pi --mode rpc` integration for agent steps. The Pi child uses a separate process group registered with the server. The runner stops that group during normal completion. Cancellation gives the runner a bounded cleanup interval, and the server reaps the registered group if the runner exits first.
399
410
 
400
- The headless child receives only the workflow step prompt, configured model arguments, and the bridge extension. Its submission uses the same step and attempt contract as origin-session work. A headless run cannot use a visible assistant-message step because it has no origin Pi session.
411
+ The headless child receives only the workflow step prompt, configured model arguments, and the bridge extension. Its submission uses the same exact request contract as origin-session work. A headless run cannot use a visible assistant-message step because it has no origin Pi session.
401
412
 
402
413
  The run binding records `interactive` or `headless` execution mode. Viewers show that mode without exposing provider credentials.
403
414