@osolmaz/pi-workflows 0.13.4 → 0.14.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 (39) hide show
  1. package/README.md +136 -118
  2. package/dist/controllers/index.d.ts +1 -1
  3. package/dist/controllers/index.js.map +1 -1
  4. package/dist/controllers/sqlite.d.ts +34 -31
  5. package/dist/controllers/sqlite.js +116 -77
  6. package/dist/controllers/sqlite.js.map +1 -1
  7. package/dist/extension/index.js +721 -202
  8. package/dist/extension/index.js.map +1 -1
  9. package/dist/extension/restart-policy.d.ts +38 -0
  10. package/dist/extension/restart-policy.js +116 -0
  11. package/dist/extension/restart-policy.js.map +1 -0
  12. package/dist/extension/terminal-decision.d.ts +51 -0
  13. package/dist/extension/terminal-decision.js +110 -0
  14. package/dist/extension/terminal-decision.js.map +1 -0
  15. package/dist/state/prune.js +36 -10
  16. package/dist/state/prune.js.map +1 -1
  17. package/dist/workflows/tool-input.d.ts +4 -0
  18. package/dist/workflows/tool-input.js +6 -1
  19. package/dist/workflows/tool-input.js.map +1 -1
  20. package/docs/2026-08-25-workflow-follow-ups.md +8 -6
  21. package/docs/DEFERRED_TURNS.md +39 -26
  22. package/docs/HUMAN_DECISIONS.md +12 -4
  23. package/docs/SQLITE_STATE.md +24 -0
  24. package/docs/plans/2026-08-19-human-decision-gates-plan.md +34 -8
  25. package/docs/plans/2026-08-27-workflow-terminal-restart-plan.md +357 -0
  26. package/docs/workflows.md +85 -29
  27. package/herdr-plugin.toml +1 -1
  28. package/package.json +1 -1
  29. package/skills/autodoc/SKILL.md +1 -1
  30. package/skills/autoimplement/SKILL.md +1 -1
  31. package/skills/autoplan/SKILL.md +1 -1
  32. package/skills/pi-workflows/SKILL.md +2 -0
  33. package/src/controllers/index.ts +3 -0
  34. package/src/controllers/sqlite.ts +226 -155
  35. package/src/extension/index.ts +881 -220
  36. package/src/extension/restart-policy.ts +163 -0
  37. package/src/extension/terminal-decision.ts +172 -0
  38. package/src/state/prune.ts +35 -9
  39. package/src/workflows/tool-input.ts +9 -1
@@ -1,8 +1,8 @@
1
1
  # Deferred workflow turns
2
2
 
3
- This specification defines how Pi Workflows schedules one successor agent turn after a workflow event stops or strands the current turn. It covers cancellation, timeout, terminal failure, launch failure, controller interruption, and claim loss.
3
+ This specification defines how Pi Workflows schedules one successor agent turn after a workflow event stops or strands the current turn. It covers every top-level interactive terminal result, cancellation, timeout, launch failure, controller interruption, and claim loss.
4
4
 
5
- The implementation plan is [Guarantee one successor turn after workflow interruption](plans/2026-08-21-deferred-turn-intents-plan.md).
5
+ The implementation plans are [Guarantee one successor turn after workflow interruption](plans/2026-08-21-deferred-turn-intents-plan.md) and [Workflow terminal decision and restart](plans/2026-08-27-workflow-terminal-restart-plan.md).
6
6
 
7
7
  ## Terms
8
8
 
@@ -18,10 +18,10 @@ The implementation plan is [Guarantee one successor turn after workflow interrup
18
18
  Each eligible source event creates at most one turn intent. Exactly one of these message paths can resolve it:
19
19
 
20
20
  1. a workflow agent prompt;
21
- 2. a completed or waiting result presentation;
21
+ 2. a result presentation;
22
22
  3. a factual fallback.
23
23
 
24
- All three paths claim the same intent before sending. A resolved intent cannot start another turn.
24
+ Every top-level interactive terminal run owns one intent. Its presentation and fallback claim that same intent before sending. A waiting presentation can resolve an earlier interruption intent, but waiting state does not create a terminal intent. A resolved intent cannot start another turn.
25
25
 
26
26
  Cancellation and process termination remain immediate. Pi Workflows does not keep the old assistant turn alive and does not wait for the successor before stopping active work.
27
27
 
@@ -56,25 +56,25 @@ Resolution records message delivery into the Pi session or the presence of the s
56
56
  Valid causes are:
57
57
 
58
58
  ```text
59
- agentCancelled | timedOut | failed | launchFailed | controllerInterrupted | claimLost
59
+ agentCancelled | timedOut | failed | launchFailed | controllerInterrupted | claimLost | terminal | cancelled
60
60
  ```
61
61
 
62
62
  The event policy is:
63
63
 
64
- | Event | Intent | Fallback rule |
65
- | -------------------------------------------------------------- | ------------------------------------------------------ | -------------------------------------------------------------------------------------- |
66
- | Agent calls `workflow cancel` during an active workflow turn | Create before `ctx.abort()` when storage is available. | Make eligible after durable terminal cancellation. |
67
- | Agent step times out | Create before `ctx.abort()` when storage is available. | Keep ineligible while a recovery prompt can arrive. Make eligible on terminal timeout. |
68
- | Active workflow turn ends in terminal failure | Create or reuse the abort intent. | Make eligible after durable terminal failure. |
69
- | Workflow reports started, then crashes before its first prompt | Create after durable failure. | Create as eligible. |
70
- | Queued launch activation fails | Create after the queue row is durably failed. | Create as eligible. |
71
- | Controller interrupts an active workflow turn | Create before the turn abort when possible. | Durable terminal or handoff state decides eligibility. |
72
- | Active workflow turn loses its queue claim | Create before the turn abort when possible. | Keep ineligible while a new owner can continue. |
73
- | Completed or waiting result has a presentation | Do not create a new intent. | Resolve an existing intent through presentation. |
74
- | Direct `/workflow cancel` | Do not create. | No automatic model turn. |
75
- | Workflow pause | Do not create. | No automatic model turn. |
76
- | User Escape or held workflow | Do not create. | No automatic model turn. |
77
- | Session shutdown | Do not create. | A closing session cannot start another turn. |
64
+ | Event | Intent | Fallback rule |
65
+ | -------------------------------------------------------------- | --------------------------------------------- | ---------------------------------------------------------------------------- |
66
+ | Top-level interactive run completes | Create one terminal intent. | Presentation and factual fallback compete for the intent. |
67
+ | Top-level interactive run fails or times out | Create or reuse the abort intent. | Make eligible after the terminal state is durable. |
68
+ | Agent or user cancels a top-level run | Create before turn abort when needed. | Make eligible after durable cancellation; the decision defaults to stopping. |
69
+ | Workflow reports started, then crashes before its first prompt | Create after durable failure. | Create as eligible. |
70
+ | Queued launch activation fails | Create after the queue row is durably failed. | Create as eligible. |
71
+ | Controller interrupts an active workflow turn | Create before the turn abort when possible. | Durable terminal or handoff state decides eligibility. |
72
+ | Active workflow turn loses its queue claim | Create before the turn abort when possible. | Keep ineligible while a new owner can continue. |
73
+ | Waiting result has a presentation | Do not create a terminal intent. | It can resolve an earlier interruption intent through presentation. |
74
+ | Controller child or internally owned run ends | Do not create a terminal intent. | Its owner receives the result. |
75
+ | Workflow pause | Do not create. | No automatic model turn. |
76
+ | User Escape or held workflow | Do not create. | No automatic model turn. |
77
+ | Session shutdown | Do not create. | A closing session cannot start another turn. |
78
78
 
79
79
  A terminal failure after a successful start tool result is eligible even when it did not first call `ctx.abort()`. This rule covers asynchronous runtime validation and startup failures that the user can see in the UI but the model cannot see in its current context.
80
80
 
@@ -232,10 +232,22 @@ The message type is `pi-workflows-deferred-turn`. Delivery uses:
232
232
  }
233
233
  ```
234
234
 
235
- The visible content reports observed facts and asks the agent to inspect durable state before it decides whether an authorized correction is needed. It does not claim that recovery occurred, resume the old run, or retry work.
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.
236
+
237
+ 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.
236
238
 
237
239
  Fallback delivery is disabled during session shutdown and while a user-interrupted workflow is held.
238
240
 
241
+ ## Selected launch and restart
242
+
243
+ A terminal decision turn can reserve at most one workflow launch: `restart`, Monitor through normal `start`, or another workflow through normal `start`. The reservation records the source terminal intent, model tool call, and request fingerprint in the new run's existing launch options. It does not activate before `agent_settled`.
244
+
245
+ Repeating the same tool call adopts the existing reservation or run. A different launch from the same terminal intent fails. Session-start and queue recovery activate a surviving reservation once when the session is idle.
246
+
247
+ `restart` accepts the terminal run ID. It checks session ownership, terminal state, explicit cancellation, source identity and revision, repeated failure, and the restart limit. It creates a new immutable run from the exact stored reference, input, and safe launch settings. The old run does not change.
248
+
249
+ Restart lineage in launch options records the root run ID, parent run ID, restart number, and parent terminal fingerprint. The fingerprint covers workflow identity and revision, exact input, terminal state, canonical result or error, and terminal reason. It excludes timestamps and run IDs. The same fingerprint cannot restart twice in one chain. A chain permits three restarts after the original run. Starting Monitor does not add restart lineage.
250
+
239
251
  ## Delivery recovery
240
252
 
241
253
  Every resolving message includes the intent ID in its custom-message details.
@@ -262,7 +274,7 @@ Pending `launch_failure` rows from the earlier alpha contract are incompatible.
262
274
 
263
275
  ## Post-completion follow-ups
264
276
 
265
- Deferred turns repair one stranded workflow turn after interruption. Ordered post-completion prompts are different. They represent user-requested normal work after successful completion, use `workflow_follow_up_queues` and `workflow_follow_ups`, and are delivered by the separate follow-up coordinator. Neither feature reads or changes the other's rows.
277
+ Deferred turns provide the terminal decision before ordered post-completion prompts. Those prompts represent user-requested normal work after successful completion, use `workflow_follow_up_queues` and `workflow_follow_ups`, and are delivered by the separate follow-up coordinator after the terminal intent is resolved. Neither feature reads or changes the other's rows.
266
278
 
267
279
  See [Continue normal work after a workflow finishes](2026-08-25-workflow-follow-ups.md).
268
280
 
@@ -293,12 +305,13 @@ This is an alpha hard cutover.
293
305
 
294
306
  An implementation conforms when:
295
307
 
296
- - one eligible source event produces at most one intent;
297
- - one intent produces at most one successor message;
298
- - an agent self-cancel receives one fallback after settlement;
308
+ - every top-level interactive terminal run produces at most one intent and one decision message;
309
+ - an agent self-cancel and a direct cancellation receive one fallback after settlement;
299
310
  - an asynchronous crash after a successful start result receives one fallback after settlement;
300
311
  - a natural recovery prompt or presentation suppresses fallback by resolving the same intent;
301
- - direct cancellation, pause, Escape, user hold, and shutdown produce no automatic turn;
312
+ - waiting checkpoints, controller children, internal owners, pause, Escape, user hold, and shutdown do not create terminal turns;
302
313
  - claim transfer permits natural resolution by the new owner and prevents stale writes;
303
- - polling, restart, lease expiry, and send-before-resolution failure do not duplicate turns;
314
+ - one terminal turn reserves at most one launch and activates it after `agent_settled`;
315
+ - exact replay adopts the same launch, while reload, lease expiry, and send-before-resolution failure do not duplicate turns or runs;
316
+ - restart keeps the prior run immutable, preserves exact input, rejects cancellation and repeated fingerprints, and stops after three restarts;
304
317
  - passive workflow notifications keep their current behavior.
@@ -322,19 +322,23 @@ Recovery follows these rules:
322
322
 
323
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.
324
324
 
325
+ 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.
326
+
327
+ 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.
328
+
325
329
  ## Compatibility
326
330
 
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.
331
+ 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.
328
332
 
329
333
  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.
330
334
 
331
335
  ## Contract impact
332
336
 
333
337
  - **Session state:** Pi records normal workflow messages and interactive decision results.
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.
338
+ - **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. It adds no new persistent shape. The private channel index remains rebuildable.
335
339
  - **Pi internals:** none.
336
340
  - **Public Pi API:** documented extension lifecycle and UI methods only.
337
- - **Public pi-workflows API:** typed human choices, `humanDecision().onTimeout`, `humanDecisionEdge()`, the channel interface, the plan approval policy, and the shared plan-change workflow.
341
+ - **Public pi-workflows API:** typed human choices, `humanDecision().onTimeout`, `humanDecisionEdge()`, the channel interface, the plan approval policy, the shared plan-change workflow, and the additive queue prepare-or-adopt operation.
338
342
 
339
343
  ## Verification requirements
340
344
 
@@ -350,6 +354,10 @@ The implementation must test:
350
354
  - unauthorized users and chats;
351
355
  - stale request digests;
352
356
  - concurrent Pi and Telegram answers;
357
+ - a direct verified answer racing recovery for the same accepted decision;
358
+ - one claim generation, one engine start, one continuation, and one execution of each continuation node;
359
+ - compatible continuation adoption without lease, queue, timestamp, or event mutation;
360
+ - incompatible continuation identity reuse without mutation;
353
361
  - identical and conflicting retries;
354
362
  - crashes before and after answer acceptance and continuation creation;
355
363
  - ambiguous Telegram sends;
@@ -357,4 +365,4 @@ The implementation must test:
357
365
  - decision cancellation and expiry;
358
366
  - included `plan-approval` routes and bounded replan loops;
359
367
  - viewer redaction; and
360
- - real Pi execution without real Telegram credentials or network calls.
368
+ - real Pi execution without real Telegram credentials or network calls, duplicate-start failures, revision conflicts, or a stranded running continuation.
@@ -111,6 +111,30 @@ The shared records do not replace domain schemas. The following `STRICT` tables
111
111
 
112
112
  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
113
 
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.
137
+
114
138
  ## Content-addressed values
115
139
 
116
140
  `blobs` stores canonical JSON and UTF-8 text as bytes. Its primary key is the 32-byte SHA-256 digest of the bytes.
@@ -52,6 +52,11 @@ Pi and Telegram implement one channel interface. Workflows address a named audie
52
52
  - Accept the first valid response with a no-replace write.
53
53
  - Adopt identical retries and reject conflicting responses.
54
54
  - Derive one continuation identity from the accepted decision.
55
+ - Prepare that continuation through one atomic queue operation.
56
+ - Let the first caller create and claim the queue row.
57
+ - Let compatible concurrent or repeated callers adopt the row without changing its lease, claim generation, queue state, timestamps, or events.
58
+ - Reject incompatible workflow source, definition, input, launch options, parent, or owning session without changing the existing row.
59
+ - Give only the winning caller a claim token and permission to start the engine.
55
60
  - Reject stale responses by decision ID and canonical request digest.
56
61
  - Rebuild the pending-decision index from immutable records.
57
62
 
@@ -83,6 +88,10 @@ Pi and Telegram implement one channel interface. Workflows address a named audie
83
88
  - Promise exactly-once Telegram message creation after an ambiguous Bot API response.
84
89
  - Add arbitrary forms in the first release. Choice buttons and one text input cover the required flows.
85
90
  - Change existing checkpoints or historical run bundles.
91
+ - Add a database field, table, migration, schema version, compatibility reader, or dual-write path for continuation startup.
92
+ - Coordinate continuation startup with a process-local promise map, timing guard, retry loop, or swallowed error.
93
+ - Change general queue claim behavior for unrelated launch paths.
94
+ - Repair an already stranded live continuation as part of this change.
86
95
  - Enable human approval by default in existing built-in workflows.
87
96
 
88
97
  ## Design decisions
@@ -107,7 +116,13 @@ The Telegram adapter accepts replan text only as a reply to the exact `ForceRepl
107
116
 
108
117
  ### Make answer acceptance exact
109
118
 
110
- Channel delivery can be retried or fail independently. Decision acceptance is one atomic no-replace operation. The accepted response and deterministic continuation identity prevent two channels from starting two continuations.
119
+ Channel delivery can be retried or fail independently. Decision acceptance is one atomic no-replace operation. The accepted response and deterministic continuation identity prevent two channels from selecting different continuations.
120
+
121
+ ### Prepare or adopt one continuation
122
+
123
+ Direct answer handling and periodic recovery can both see the accepted decision. They use one continuation coordinator and one durable queue operation. The operation creates and claims a missing queue row or returns a compatible existing row as adopted. Only a newly claimed preparation can call `WorkflowEngine.continueRun()`.
124
+
125
+ An adopted result is normal success. It does not start another engine or change the winning lease, claim generation, queue state, timestamps, or events. The adopter receives no claim token, so it cannot renew, release, park, complete, or replace the winning claim. Incompatible reuse fails without mutation. Existing cleanup handles failure before run initialization, and existing lease expiry and activation recovery handle a winning process that stops after preparation.
111
126
 
112
127
  ### Handle Telegram delivery limits honestly
113
128
 
@@ -171,10 +186,17 @@ A private SQLite index may track pending decisions, channel leases, Telegram upd
171
186
  - Validate the accepted response through the node contract.
172
187
  - Preserve the parent's original workflow input.
173
188
  - Expose the response as the checkpoint output.
174
- - derive and adopt the continuation identity; and
175
- - leave the legacy checkpoint path unchanged.
176
-
177
- Test crashes before acceptance, after acceptance, during continuation creation, and after continuation completion.
189
+ - Derive the deterministic continuation identity.
190
+ - Add a typed atomic prepare-or-adopt operation to the existing SQLite queue store.
191
+ - Compare the stored workflow source, definition digest, input, launch options, parent, and owning session before adoption.
192
+ - Build one prepared continuation value in the Pi extension.
193
+ - Route direct verified answers and recovery through one continuation coordinator.
194
+ - Start `WorkflowEngine.continueRun()` only with the new claim token.
195
+ - Return normal started or already-continuing success to the answer path.
196
+ - Keep enqueue and lease behavior unchanged for other launch paths.
197
+ - Leave legacy checkpoint continuation unchanged.
198
+
199
+ Test crashes before acceptance, after acceptance, during continuation creation, and after continuation completion. Add a focused race test for direct answer handling and recovery. It must prove one claim generation, one engine start, one continuation, one execution of each continuation node, coherent queue and run state, and no duplicate-start or revision-conflict failure. Add a real-Pi version of the same regression with a durable barrier instead of sleep-only timing.
178
200
 
179
201
  ### Channel management
180
202
 
@@ -248,7 +270,9 @@ The setup command uses an existing mode-`0600` token file instead of collecting
248
270
  - The workflow tool cannot answer a protected human decision.
249
271
  - Pi and Telegram can receive the same decision through one audience profile.
250
272
  - The first concurrent valid answer wins and creates one continuation.
251
- - An identical retry is adopted; a conflicting or stale answer is rejected.
273
+ - Direct answer handling and recovery can race without starting it twice or replacing its lease.
274
+ - The first queue preparation starts the engine, and a compatible repeat adopts it without mutation.
275
+ - An incompatible continuation identity, conflicting answer, or stale answer is rejected without mutation.
252
276
  - The original workflow input survives a human-decision continuation.
253
277
  - Old checkpoints and old bundles pass their existing tests unchanged.
254
278
  - The Telegram adapter accepts text only from the bound reply and approved numeric actor.
@@ -267,6 +291,8 @@ npx vitest run test/human-decision-api.test.ts test/human-decision-store.test.ts
267
291
  npx vitest run test/human-decision-engine.test.ts test/run-resume.test.ts
268
292
  npx vitest run test/pi-decision-channel.test.ts test/telegram-decision-channel.test.ts
269
293
  npx vitest run test/plan-approval.test.ts test/composition.test.ts
294
+ npx vitest run test/run-queue.test.ts test/extension.test.ts
295
+ npx vitest run --config vitest.e2e.config.ts test/e2e/workflow.e2e.test.ts
270
296
  ```
271
297
 
272
298
  Run all repository gates before review:
@@ -290,7 +316,7 @@ This work adds compatible public APIs and additive persisted records. Release it
290
316
  ## Contract impact
291
317
 
292
318
  - **Session state:** normal workflow messages and interactive decision results.
293
- - **Other persistent data:** additive decision records, a rebuildable private channel index, and private channel configuration.
319
+ - **Other persistent data:** additive decision records, a rebuildable private channel index, private channel configuration, and existing run queue and lease records. The continuation startup fix adds no persistent field, table, migration, or schema version.
294
320
  - **Pi internals:** none.
295
321
  - **Public Pi API:** documented extension lifecycle plus command and UI methods only.
296
- - **Public pi-workflows API:** typed human choices, `humanDecision()`, `humanDecisionEdge()`, the channel interface, and the `plan-approval` workflow.
322
+ - **Public pi-workflows API:** typed human choices, `humanDecision()`, `humanDecisionEdge()`, the channel interface, the `plan-approval` workflow, and an additive queue prepare-or-adopt operation.
@@ -0,0 +1,357 @@
1
+ ---
2
+ title: Workflow terminal decision and restart plan
3
+ author: Onur Solmaz <2453968+osolmaz@users.noreply.github.com>
4
+ date: 2026-08-27
5
+ ---
6
+
7
+ # Full plan
8
+
9
+ ## Goal
10
+
11
+ After every top-level workflow run ends, give the model one normal successor turn.
12
+
13
+ That turn contains the workflow result and terminal reason. The model uses the conversation it already has to decide whether to:
14
+
15
+ - stop because the user’s task is complete
16
+ - restart the same workflow as a new run
17
+ - start Monitor for an authorized external wait
18
+ - ask the user for a required decision or authority
19
+ - take another safe authorized action
20
+
21
+ The system does not save or identify the original user message. It does not restart automatically. It gives the model one clear decision opportunity and makes safe retry the default for unfinished work after technical failures.
22
+
23
+ ## Shared terminal behavior
24
+
25
+ The shared terminal message must include:
26
+
27
+ - workflow name and revision
28
+ - terminal run ID
29
+ - exact workflow input
30
+ - workflow result
31
+ - terminal state
32
+ - terminal reason
33
+ - restart count
34
+ - earlier terminal outcomes in the same restart chain
35
+
36
+ The message must tell the model:
37
+
38
+ > A workflow run ended, but that does not prove the user’s task is complete. Use the current conversation and this result to decide what to do next. If the task is unfinished because of an unexpected technical or temporary failure, prefer a safe restart. Stop if the work is complete, the user cancelled it, new authority is required, the user must make a decision, or the same failure has repeated. Use Monitor only for an authorized external wait.
39
+
40
+ Use the term **result**, not “durable result.”
41
+
42
+ ## Implementation steps
43
+
44
+ ### 1. Build one shared terminal-decision message
45
+
46
+ **Where**
47
+
48
+ - `src/extension/deferred-turn.ts`
49
+ - Add `src/extension/terminal-decision.ts` if a separate pure module keeps the code smaller.
50
+
51
+ **Change**
52
+
53
+ Add a pure builder that reads the existing run record and produces the shared terminal facts and prompt.
54
+
55
+ It must not read, copy, hash, or store an original user message.
56
+
57
+ Use the existing stored workflow input and result. Apply the existing output-size rules to large results, but always include the run ID, state, reason, and restart history.
58
+
59
+ **Verification**
60
+
61
+ Unit tests must cover completed, failed, timed-out, maxSteps, cancelled, and blocked results.
62
+
63
+ ### 2. Create one terminal turn for every top-level run
64
+
65
+ **Where**
66
+
67
+ - `finishRun` in `src/extension/index.ts`
68
+ - `src/extension/deferred-turn-coordinator.ts`
69
+ - Existing turn-intent state in `src/controllers/sqlite.ts`
70
+
71
+ **Change**
72
+
73
+ Create one terminal turn intent when a top-level interactive run reaches a terminal state.
74
+
75
+ Normal presentation and fallback delivery must compete for the same intent. The first successful delivery settles it. Every later delivery attempt becomes a no-op.
76
+
77
+ Do not create this turn for:
78
+
79
+ - waiting checkpoints
80
+ - controller child runs
81
+ - internal helper runs that already report to an owner
82
+
83
+ **Verification**
84
+
85
+ Race tests must prove that presentation, fallback, reload recovery, and crash recovery produce one model turn, not two.
86
+
87
+ ### 3. Replace the current terminal presentation instruction
88
+
89
+ **Where**
90
+
91
+ - `buildPresentationMessage`
92
+ - `buildDeferredTurnContent`
93
+ - Related presentation helpers in `src/extension/index.ts`
94
+
95
+ **Change**
96
+
97
+ Remove the current instruction that says the model must not call the workflow tool.
98
+
99
+ Replace it with the shared decision instructions. The terminal turn must permit one workflow launch selected by the model.
100
+
101
+ Completed runs still get a result turn. A workflow state of `completed` does not always mean the larger user task is complete. For example, the result can say that work is blocked.
102
+
103
+ Explicit human cancellation must default to stopping.
104
+
105
+ **Verification**
106
+
107
+ Tests must prove that the model can select restart or Monitor during the terminal turn and that ordinary completed work does not cause an automatic restart.
108
+
109
+ ### 4. Add a generic restart action
110
+
111
+ **Where**
112
+
113
+ - `src/workflows/tool-input.ts`
114
+ - Workflow tool registration, schema, help text, and control switch under `src/workflows/`
115
+ - Restart handling in `src/extension/index.ts`
116
+
117
+ **Contract**
118
+
119
+ ```json
120
+ {
121
+ "action": "restart",
122
+ "runId": "terminal-run-id"
123
+ }
124
+ ```
125
+
126
+ **Change**
127
+
128
+ The action must:
129
+
130
+ 1. Read the terminal run.
131
+ 2. Confirm that it belongs to the current session.
132
+ 3. Confirm that it is terminal.
133
+ 4. Reuse the exact workflow reference, input, and safe launch settings.
134
+ 5. Create a new immutable run.
135
+ 6. Record the restart relationship.
136
+ 7. Leave the old run unchanged.
137
+
138
+ Reject restart when:
139
+
140
+ - the run is active or waiting
141
+ - the run is unknown
142
+ - the run belongs to another session
143
+ - the run was explicitly cancelled
144
+ - the workflow source or revision is no longer available
145
+ - the restart limit was reached
146
+ - the same terminal failure already repeated
147
+
148
+ A later explicit user request can still use normal `start`.
149
+
150
+ **Verification**
151
+
152
+ Tool-schema and extension tests must prove exact input reuse, immutable old runs, session checks, source checks, and correct rejection behavior.
153
+
154
+ ### 5. Permit one selected launch during the terminal turn
155
+
156
+ **Where**
157
+
158
+ - `queueToolLaunch`
159
+ - presentation tracking in `src/extension/index.ts`
160
+ - `agent_settled`
161
+ - existing queued-launch recovery
162
+
163
+ **Change**
164
+
165
+ The current presentation guard rejects workflow launches. Add one narrow exception for the active terminal-decision turn.
166
+
167
+ The model can reserve one of these:
168
+
169
+ - restart
170
+ - Monitor
171
+ - another workflow start
172
+
173
+ The reservation must not activate until the model turn settles. A second workflow launch from the same terminal turn must fail.
174
+
175
+ Other tool calls remain subject to their normal rules.
176
+
177
+ **Verification**
178
+
179
+ Tests must prove that:
180
+
181
+ - one launch can be reserved during presentation
182
+ - it starts only after `agent_settled`
183
+ - a second launch is rejected
184
+ - reload after reservation does not lose it
185
+ - crash recovery does not start it twice
186
+
187
+ ### 6. Add bounded restart lineage
188
+
189
+ **Where**
190
+
191
+ - Add `src/extension/restart-policy.ts`
192
+ - Existing run launch-options JSON and accessors
193
+ - No new database table
194
+
195
+ **Change**
196
+
197
+ Store this information for restarted runs:
198
+
199
+ - root run ID
200
+ - parent run ID
201
+ - restart number
202
+ - parent terminal fingerprint
203
+
204
+ A terminal fingerprint is a stable hash of:
205
+
206
+ - workflow identity and revision
207
+ - exact input
208
+ - terminal state
209
+ - canonical result or error
210
+ - canonical terminal reason
211
+
212
+ Do not include timestamps or new run IDs in the fingerprint.
213
+
214
+ Allow at most three restart actions after the original run. This permits at most four runs in one chain.
215
+
216
+ If a terminal fingerprint occurs again in the same chain, reject another restart immediately. If the result changes because the workflow made progress, another restart can remain eligible until the total limit is reached.
217
+
218
+ Starting Monitor does not consume a restart.
219
+
220
+ **Verification**
221
+
222
+ Tests must cover:
223
+
224
+ - first technical retry
225
+ - progress followed by a different failure
226
+ - repeated identical maxSteps failure
227
+ - three-restart limit
228
+ - Monitor selection
229
+ - restart history after database reopen
230
+
231
+ ### 7. Make restart reservation idempotent
232
+
233
+ **Where**
234
+
235
+ - Existing effect records
236
+ - Existing run queue and reservation code
237
+ - Terminal turn-intent settlement code
238
+
239
+ **Change**
240
+
241
+ Key the selected launch to the source terminal turn intent and tool call.
242
+
243
+ If the host repeats the same tool call after a crash or reload, return the existing reservation or new run instead of creating another one.
244
+
245
+ The terminal turn intent, launch reservation, and resulting run must have one inspectable chain.
246
+
247
+ **Verification**
248
+
249
+ Inject failures after:
250
+
251
+ - turn-intent claim
252
+ - launch reservation
253
+ - run creation
254
+ - terminal response settlement
255
+
256
+ After recovery, there must still be one terminal message and one successor run.
257
+
258
+ ### 8. Document the contract
259
+
260
+ **Where**
261
+
262
+ - `docs/workflows.md`
263
+ - `docs/SQLITE_STATE.md`
264
+ - Workflow tool reference and examples
265
+ - Relevant README text
266
+
267
+ **Change**
268
+
269
+ Document:
270
+
271
+ - the shared terminal decision turn
272
+ - the `restart` action
273
+ - the difference between a workflow ending and the user’s task finishing
274
+ - retry defaults and limits
275
+ - Monitor selection
276
+ - explicit cancellation behavior
277
+ - top-level versus child-run behavior
278
+ - recovery and duplicate prevention
279
+ - that conversation context remains owned by Pi
280
+ - that Pi Workflows does not capture or persist an original user message
281
+
282
+ No workflow definition needs an opt-in or terminal restart step.
283
+
284
+ ## Contract changes
285
+
286
+ - The workflow tool gains `restart`.
287
+ - Every top-level terminal run owns one terminal turn intent.
288
+ - Restart always creates a new run.
289
+ - Restart reuses the exact prior workflow input.
290
+ - The model makes the continuation decision from the current conversation.
291
+ - Restart is preferred, not forced, for unfinished work after technical or temporary failures.
292
+ - Explicit cancellation, missing authority, required user decisions, repeated failures, and completed work stop.
293
+ - Restart lineage uses existing run launch data.
294
+ - No new store, service, controller, or Pi API is added.
295
+ - No original-message provenance contract is added.
296
+
297
+ ## Test plan
298
+
299
+ Add regression coverage for:
300
+
301
+ 1. Successful completion produces one result turn and no automatic restart.
302
+ 2. A blocked result from a completed workflow lets the model select restart.
303
+ 3. Failed, timed-out, and maxSteps runs offer restart.
304
+ 4. Explicit cancellation is not restartable through the shortcut.
305
+ 5. Waiting checkpoints do not produce a terminal turn.
306
+ 6. Controller child runs do not produce competing turns.
307
+ 7. Presentation and fallback races produce one turn.
308
+ 8. Restart uses the exact workflow reference and input.
309
+ 9. Restart leaves the prior run unchanged.
310
+ 10. Monitor starts through the normal start path.
311
+ 11. A selected launch waits for `agent_settled`.
312
+ 12. Reload and crash recovery do not duplicate turns or runs.
313
+ 13. The same terminal failure cannot repeat indefinitely.
314
+ 14. A chain cannot exceed three restarts.
315
+ 15. No new code captures, hashes, or stores an original user message.
316
+ 16. The maxSteps failure that caused this incident produces a terminal decision turn instead of silently ending the task.
317
+
318
+ Run the full repository checks:
319
+
320
+ ```bash
321
+ npm run check
322
+ npm run test:e2e
323
+ npx slophammer-ts@latest dry .
324
+ npx slophammer-ts@latest check . --only ts.dependency-boundaries-required
325
+ ```
326
+
327
+ ## Main risks
328
+
329
+ - **The model restarts completed work.**
330
+ Make stopping the default for successful results and enforce restart limits.
331
+
332
+ - **A retry repeats external effects.**
333
+ Include the prior result and restart history so the model can inspect the current state before it retries.
334
+
335
+ - **Presentation and fallback both fire.**
336
+ Make both settle the same turn intent.
337
+
338
+ - **A launch starts while the terminal response is still active.**
339
+ Reserve it first and activate it after `agent_settled`.
340
+
341
+ - **A temporary outage causes a loop.**
342
+ Use Monitor for external waits, stop repeated fingerprints, and allow only three restarts.
343
+
344
+ ## Boundaries
345
+
346
+ Do not:
347
+
348
+ - modify Pi core or private APIs
349
+ - store or identify an original user message
350
+ - add restart nodes to individual workflows
351
+ - modify Autoimplement, Monitor, or other workflow definitions
352
+ - create a new controller, database, service, or daemon
353
+ - bypass cancellation, checkpoints, reviews, CI, authority, or safety rules
354
+ - add compatibility shims or parallel state contracts
355
+ - release or deploy anything as part of this plan
356
+
357
+ This is the selected plan.