@osolmaz/pi-workflows 0.15.3 → 0.16.1

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 (207) hide show
  1. package/README.md +24 -8
  2. package/dist/channels/adapter-entry.d.ts +3 -0
  3. package/dist/channels/adapter-entry.js +203 -0
  4. package/dist/channels/adapter-entry.js.map +1 -0
  5. package/dist/channels/config.d.ts +43 -0
  6. package/dist/channels/config.js +234 -0
  7. package/dist/channels/config.js.map +1 -0
  8. package/dist/channels/protocol.d.ts +94 -0
  9. package/dist/channels/protocol.js +219 -0
  10. package/dist/channels/protocol.js.map +1 -0
  11. package/dist/channels/telegram.d.ts +46 -0
  12. package/dist/channels/telegram.js +285 -0
  13. package/dist/channels/telegram.js.map +1 -0
  14. package/dist/client/activity.d.ts +2 -0
  15. package/dist/client/activity.js +6 -0
  16. package/dist/client/activity.js.map +1 -0
  17. package/dist/client/client.d.ts +102 -0
  18. package/dist/client/client.js +737 -0
  19. package/dist/client/client.js.map +1 -0
  20. package/dist/client/index.d.ts +3 -0
  21. package/dist/client/index.js +3 -0
  22. package/dist/client/index.js.map +1 -0
  23. package/dist/client/materialize.d.ts +7 -0
  24. package/dist/client/materialize.js +177 -0
  25. package/dist/client/materialize.js.map +1 -0
  26. package/dist/client/protocol.d.ts +60 -0
  27. package/dist/client/protocol.js +275 -0
  28. package/dist/client/protocol.js.map +1 -0
  29. package/dist/client/resolver.d.ts +23 -0
  30. package/dist/client/resolver.js +2 -0
  31. package/dist/client/resolver.js.map +1 -0
  32. package/dist/client/view.d.ts +144 -0
  33. package/dist/client/view.js +3 -0
  34. package/dist/client/view.js.map +1 -0
  35. package/dist/controllers/index.d.ts +1 -1
  36. package/dist/controllers/index.js.map +1 -1
  37. package/dist/controllers/sqlite.d.ts +47 -157
  38. package/dist/controllers/sqlite.js +230 -406
  39. package/dist/controllers/sqlite.js.map +1 -1
  40. package/dist/extension/index.d.ts +23 -1
  41. package/dist/extension/index.js +734 -555
  42. package/dist/extension/index.js.map +1 -1
  43. package/dist/extension/recorder.d.ts +95 -0
  44. package/dist/extension/recorder.js +530 -0
  45. package/dist/extension/recorder.js.map +1 -0
  46. package/dist/extension/remote-recorder-store.d.ts +25 -0
  47. package/dist/extension/remote-recorder-store.js +81 -0
  48. package/dist/extension/remote-recorder-store.js.map +1 -0
  49. package/dist/extension/session-events.d.ts +134 -0
  50. package/dist/extension/session-events.js +60 -0
  51. package/dist/extension/session-events.js.map +1 -0
  52. package/dist/extension/session-view.d.ts +9 -2
  53. package/dist/extension/session-view.js +117 -51
  54. package/dist/extension/session-view.js.map +1 -1
  55. package/dist/extension/step-message.d.ts +6 -4
  56. package/dist/extension/step-message.js +12 -6
  57. package/dist/extension/step-message.js.map +1 -1
  58. package/dist/extension/widget.d.ts +3 -2
  59. package/dist/extension/widget.js +41 -24
  60. package/dist/extension/widget.js.map +1 -1
  61. package/dist/extension/workflow-message-coordinator.d.ts +27 -0
  62. package/dist/extension/workflow-message-coordinator.js +294 -0
  63. package/dist/extension/workflow-message-coordinator.js.map +1 -0
  64. package/dist/host/channel-effects.d.ts +72 -0
  65. package/dist/host/channel-effects.js +271 -0
  66. package/dist/host/channel-effects.js.map +1 -0
  67. package/dist/host/channel-supervisor.d.ts +21 -0
  68. package/dist/host/channel-supervisor.js +54 -0
  69. package/dist/host/channel-supervisor.js.map +1 -0
  70. package/dist/host/child-worker-supervisor.js +1 -1
  71. package/dist/host/child-worker-supervisor.js.map +1 -1
  72. package/dist/host/resolver-entry.d.ts +2 -23
  73. package/dist/host/resolver-entry.js +1 -1
  74. package/dist/host/resolver-entry.js.map +1 -1
  75. package/dist/host/runner.d.ts +54 -8
  76. package/dist/host/runner.js +1997 -305
  77. package/dist/host/runner.js.map +1 -1
  78. package/dist/host/state.d.ts +25 -19
  79. package/dist/host/state.js +303 -89
  80. package/dist/host/state.js.map +1 -1
  81. package/dist/host/view.d.ts +83 -0
  82. package/dist/host/view.js +975 -0
  83. package/dist/host/view.js.map +1 -0
  84. package/dist/host/worker-protocol.js +1 -1
  85. package/dist/host/worker-protocol.js.map +1 -1
  86. package/dist/state/database.d.ts +1 -0
  87. package/dist/state/database.js +15 -0
  88. package/dist/state/database.js.map +1 -1
  89. package/dist/state/prune.d.ts +3 -1
  90. package/dist/state/prune.js +9 -33
  91. package/dist/state/prune.js.map +1 -1
  92. package/dist/state/schema.js +88 -98
  93. package/dist/state/schema.js.map +1 -1
  94. package/dist/state/workflow-messages.d.ts +96 -0
  95. package/dist/state/workflow-messages.js +335 -0
  96. package/dist/state/workflow-messages.js.map +1 -0
  97. package/dist/viewer/backup.d.ts +2 -0
  98. package/dist/viewer/backup.js +28 -0
  99. package/dist/viewer/backup.js.map +1 -0
  100. package/dist/viewer/cli.d.ts +4 -0
  101. package/dist/viewer/cli.js +150 -170
  102. package/dist/viewer/cli.js.map +1 -1
  103. package/dist/viewer/render.js +1 -1
  104. package/dist/viewer/render.js.map +1 -1
  105. package/dist/viewer/tui.d.ts +5 -7
  106. package/dist/viewer/tui.js +245 -108
  107. package/dist/viewer/tui.js.map +1 -1
  108. package/dist/workflows/human-decision.d.ts +2 -0
  109. package/dist/workflows/human-decision.js +8 -2
  110. package/dist/workflows/human-decision.js.map +1 -1
  111. package/dist/workflows/index.d.ts +1 -1
  112. package/dist/workflows/index.js.map +1 -1
  113. package/dist/workflows/settings.d.ts +1 -8
  114. package/dist/workflows/settings.js.map +1 -1
  115. package/dist/workflows/store.d.ts +67 -20
  116. package/dist/workflows/store.js +451 -378
  117. package/dist/workflows/store.js.map +1 -1
  118. package/dist/workflows/tool-input.d.ts +21 -0
  119. package/dist/workflows/tool-input.js +23 -1
  120. package/dist/workflows/tool-input.js.map +1 -1
  121. package/dist/workflows/types.d.ts +5 -5
  122. package/dist/workflows/workflow-message-content.d.ts +38 -0
  123. package/dist/workflows/workflow-message-content.js +157 -0
  124. package/dist/workflows/workflow-message-content.js.map +1 -0
  125. package/docs/2026-08-18-herdr-piw-plan.md +2 -1
  126. package/docs/2026-08-20-durable-workflow-launch-plan.md +1 -1
  127. package/docs/2026-08-25-workflow-follow-ups.md +26 -50
  128. package/docs/2026-08-30-out-of-process-workflow-host-plan.md +3 -3
  129. package/docs/2026-09-01-restore-session-delivery-controls-plan.md +4 -0
  130. package/docs/2026-09-01-unified-workflow-client-plan.md +385 -0
  131. package/docs/2026-09-02-installed-live-e2e-plan.md +239 -0
  132. package/docs/2026-09-02-unify-workflow-messages-plan.md +562 -0
  133. package/docs/DEFERRED_TURNS.md +66 -294
  134. package/docs/HUMAN_DECISIONS.md +29 -30
  135. package/docs/HUMAN_DECISION_PRESENTATIONS.md +4 -11
  136. package/docs/SQLITE_STATE.md +31 -25
  137. package/docs/WORKFLOW_HOST.md +155 -77
  138. package/docs/WORKFLOW_STEP_MESSAGES.md +166 -75
  139. package/docs/development.md +2 -1
  140. package/docs/live-replay-protocol.md +74 -132
  141. package/docs/tui-viewer.md +12 -14
  142. package/docs/workflows.md +78 -28
  143. package/herdr-plugin.toml +1 -1
  144. package/package.json +9 -3
  145. package/plugins/herdr/viewer.mjs +14 -5
  146. package/protocol/client.v1.schema.json +137 -0
  147. package/protocol/fixtures/client-v1.json +23 -0
  148. package/src/channels/adapter-entry.ts +220 -0
  149. package/src/channels/config.ts +296 -0
  150. package/src/channels/protocol.ts +333 -0
  151. package/src/channels/telegram.ts +335 -0
  152. package/src/client/activity.ts +6 -0
  153. package/src/client/client.ts +939 -0
  154. package/src/client/index.ts +25 -0
  155. package/src/client/materialize.ts +228 -0
  156. package/src/client/protocol.ts +333 -0
  157. package/src/client/resolver.ts +26 -0
  158. package/src/client/view.ts +163 -0
  159. package/src/controllers/index.ts +0 -1
  160. package/src/controllers/sqlite.ts +339 -808
  161. package/src/extension/index.ts +853 -665
  162. package/src/extension/recorder.ts +687 -0
  163. package/src/extension/remote-recorder-store.ts +126 -0
  164. package/src/extension/session-events.ts +119 -0
  165. package/src/extension/session-view.ts +157 -51
  166. package/src/extension/step-message.ts +19 -9
  167. package/src/extension/widget.ts +53 -25
  168. package/src/extension/workflow-message-coordinator.ts +334 -0
  169. package/src/host/channel-effects.ts +465 -0
  170. package/src/host/channel-supervisor.ts +73 -0
  171. package/src/host/child-worker-supervisor.ts +1 -1
  172. package/src/host/resolver-entry.ts +11 -26
  173. package/src/host/runner.ts +2639 -450
  174. package/src/host/state.ts +404 -137
  175. package/src/host/view.ts +1212 -0
  176. package/src/host/worker-protocol.ts +1 -1
  177. package/src/state/database.ts +13 -0
  178. package/src/state/prune.ts +14 -42
  179. package/src/state/schema.ts +88 -98
  180. package/src/state/workflow-messages.ts +548 -0
  181. package/src/viewer/backup.ts +29 -0
  182. package/src/viewer/cli.ts +171 -185
  183. package/src/viewer/render.ts +1 -5
  184. package/src/viewer/tui.ts +261 -124
  185. package/src/workflows/human-decision.ts +18 -2
  186. package/src/workflows/index.ts +0 -1
  187. package/src/workflows/settings.ts +1 -20
  188. package/src/workflows/store.ts +650 -511
  189. package/src/workflows/tool-input.ts +36 -1
  190. package/src/workflows/types.ts +5 -5
  191. package/src/workflows/workflow-message-content.ts +197 -0
  192. package/dist/extension/decision-channels.d.ts +0 -134
  193. package/dist/extension/decision-channels.js +0 -1307
  194. package/dist/extension/decision-channels.js.map +0 -1
  195. package/dist/host/client.d.ts +0 -48
  196. package/dist/host/client.js +0 -216
  197. package/dist/host/client.js.map +0 -1
  198. package/dist/host/protocol.d.ts +0 -38
  199. package/dist/host/protocol.js +0 -156
  200. package/dist/host/protocol.js.map +0 -1
  201. package/dist/viewer/watch.d.ts +0 -6
  202. package/dist/viewer/watch.js +0 -46
  203. package/dist/viewer/watch.js.map +0 -1
  204. package/src/extension/decision-channels.ts +0 -1826
  205. package/src/host/client.ts +0 -293
  206. package/src/host/protocol.ts +0 -196
  207. package/src/viewer/watch.ts +0 -51
@@ -1,25 +1,67 @@
1
- # Workflow step messages
1
+ # Workflow messages in Pi
2
2
 
3
- This specification defines how pi-workflows shows agent-step instructions in an interactive Pi session. The model receives the complete step prompt, while the user sees a small workflow card that can be expanded. [Restore workflow session delivery and controls](2026-09-01-restore-session-delivery-controls-plan.md) records the delivery and session-control repair.
3
+ Status: this is the implemented workflow-message contract. [Unify workflow messages and restore hosted behavior](2026-09-02-unify-workflow-messages-plan.md) records the approved design and implementation plan.
4
4
 
5
5
  ## Goal
6
6
 
7
- Agent-step prompts contain the task, workflow identity, attempt identity, output form, and completion rules. Submitted steps call the workflow tool. Assistant-message steps reply normally. This information is required by the model, but showing it as a large user message makes the conversation hard to read.
7
+ Pi Workflows must add several kinds of content to an origin Pi conversation. These include interactive step prompts, protected human decisions, passive notifications, terminal results, and follow-up prompts. Initial, reminder, and resumed prompts are one step-message kind with different display reasons.
8
8
 
9
- pi-workflows sends the same prompt as a custom Pi message. A custom renderer shows a compact summary by default and the full content when expanded.
9
+ The host saves all of them as workflow messages. One extension component sends them through documented Pi APIs. Feature records continue to own workflow results, answers, settings, and timeouts.
10
10
 
11
- Both output forms use the existing `agent` node. The completion form changes through `expectedOutput`; no new node type is added.
11
+ The model receives complete instructions when a workflow message starts a turn. The user sees a compact card for structured workflow content and can expand it.
12
12
 
13
- ## Message contract
13
+ ## Workflow message contract
14
14
 
15
- Interactive agent-step messages use the custom type `pi-workflows-agent-step`.
15
+ A workflow message is content that Pi Workflows requires Pi to add to one conversation. It does not mean every message in that conversation.
16
16
 
17
- The message has this shape:
17
+ The message kinds are:
18
+
19
+ | Kind | Pi behavior | Purpose |
20
+ | -------------- | ----------------------------------------------- | ------------------------------------------------ |
21
+ | `step` | Custom message that starts a model turn | Initial, reminder, or resumed interactive prompt |
22
+ | `decision` | Custom message that does not start a model turn | Protected choice for a person |
23
+ | `notification` | Custom message that does not start a model turn | Passive workflow notice |
24
+ | `terminal` | Custom message that starts a model turn | Final result and safe recovery choice |
25
+ | `followUp` | Custom message that starts normal work | Work saved for after successful completion |
26
+
27
+ The host stores one `WorkflowMessage` record before Pi can send it:
28
+
29
+ ```ts
30
+ type WorkflowMessage = {
31
+ schema: "pi-workflows.workflow-message.v1";
32
+ workflowMessageId: string;
33
+ runId: string;
34
+ targetSessionId: string;
35
+ kind: "step" | "decision" | "notification" | "terminal" | "followUp";
36
+ sourceId: string;
37
+ contentDigest: string;
38
+ order: number;
39
+ status: "pending" | "sent" | "cancelled";
40
+ piSessionEntryId: string | null;
41
+ createdAt: string;
42
+ updatedAt: string;
43
+ };
44
+ ```
45
+
46
+ The exact Pi content and custom display details remain in the content-addressed value store. `contentDigest` binds the record to those bytes. `sourceId` links the message to the feature record that created it.
47
+
48
+ `kind` determines the custom renderer, whether a model turn starts, and the host eligibility rule. The host does not store duplicate flags or message-to-message pointers for those facts.
49
+
50
+ `order` is the acceptance order for one origin session. The host marks one pending item as next in the origin-session view. An earlier ineligible or cancelled item does not block unrelated eligible work.
51
+
52
+ A source lifecycle transaction can change `pending` to `cancelled`. Active-branch evidence changes `pending` or `cancelled` to `sent`; evidence wins because Pi already contains the entry. `sent` is terminal. A pending message can be new or uncertain after a process stopped, so the coordinator always checks the active branch before sending it.
53
+
54
+ Workflow message IDs and internal send state are not included in provider-facing prompt content. The hidden custom-message details contain only the stable workflow message ID needed for branch recovery.
55
+
56
+ ## Agent step card
57
+
58
+ Every step message uses the custom type `pi-workflows-step`:
18
59
 
19
60
  ```ts
20
61
  export type WorkflowAgentStepMessageDetails = {
21
62
  schema: "pi-workflows.agent-step-message.v1";
22
- kind: "step" | "reminder" | "resume";
63
+ workflowMessageId: string;
64
+ reason: "initial" | "reminder" | "resumed";
23
65
  contract: AgentStepContract;
24
66
  presentation?: {
25
67
  runTitle?: string;
@@ -29,124 +71,173 @@ export type WorkflowAgentStepMessageDetails = {
29
71
 
30
72
  pi.sendMessage(
31
73
  {
32
- customType: "pi-workflows-agent-step",
74
+ customType: "pi-workflows-step",
33
75
  content: completeModelPrompt,
34
76
  display: true,
35
77
  details,
36
78
  },
37
- {
38
- triggerTurn: true,
39
- },
79
+ { triggerTurn: true },
40
80
  );
41
81
  ```
42
82
 
43
- `content` is the complete prompt that the existing executor would send as a user message. It remains available to the model and in session history.
44
-
45
- `details` contains structured display data. The renderer reads this object directly and never parses the prompt text. `AgentStepContract` remains the source of every identity field, the completion form, the submitted output description, and any explicit assistant character limit.
46
-
47
- `kind` distinguishes the first delivery from a reminder or a resume that must repeat the instructions. An ordinary resume that can continue without another prompt does not create a message.
48
-
49
- ## Session delivery
50
-
51
- One shared coordinator delivers step prompts, protected decisions, notifications, and final workflow results. It does not claim or send a new message while Pi is busy or another message is pending. It checks these conditions again after the asynchronous host claim and immediately before the synchronous send. The coordinator remembers the stable delivery ID and exact claim expiry before that final check. If Pi became busy, a later poll can use that same claim while it remains live. Before it sends retained work, it revalidates the exact claim and durable resource through the host, then checks Pi and the lease again. Cancelled, paused, or replaced work is discarded. An expired unused claim is also discarded.
83
+ `content` is the complete provider-facing prompt. It includes the task, workflow identity, attempt identity, output form, and completion rules.
52
84
 
53
- Before the coordinator calls `pi.sendMessage()`, it records the delivery in a process-local queued map. The one-second poll can look for the matching session entry, but it cannot send that ID again. The coordinator clears the queued ID only after it observes the custom message in the active branch and saves the public Pi session entry ID through the workflow host. If Pi does not expose that entry after the confirmation interval, or if saving its receipt fails, the coordinator reports an ambiguous delivery and keeps it blocked.
85
+ The renderer reads `details` and does not parse the prompt. It shows a compact summary by default and the complete prompt when expanded. If the renderer is unavailable, Pi still retains the custom message and its content.
54
86
 
55
- Reload and restart recovery first search the branch for that stable ID. An existing entry is adopted. A new message is sent only when no entry exists and the host grants a new claim. The host does not grant a second live presentation claim. Polling treats another live claim as unavailable work rather than a workflow error.
87
+ Submitted agent steps call the `workflow` tool. Assistant-message steps reply normally. Both forms keep the existing `agent` node and use `expectedOutput` to select the completion form.
56
88
 
57
89
  ## Engine boundary
58
90
 
59
- The workflow engine remains independent of Pi. It continues to produce an `AgentStepRequest` with a complete prompt and structured contract.
91
+ The workflow engine remains independent of Pi. It produces an `AgentStepRequest` with a complete prompt and structured contract. One pure formatter builds the same provider-facing prompt for interactive and RPC execution. The extension does not shorten or rebuild it from display fields.
60
92
 
61
- The request carries optional presentation data for the run title and node status detail. The workflow host stores the prompt, contract, presentation data, and delivery kind for the origin Pi extension. The RPC executor handles submitted steps. An assistant-message step parks for the origin Pi session; a detached run with no origin session fails before prompting.
93
+ When a workflow has live settings, the formatter adds the settings scope, change number, bounded current value, allowed model paths, and exact `change-settings` action. It also adds the `queue-follow-up` and `remove-follow-up` actions. Actor identity is not model input. The extension derives it from the documented tool call.
62
94
 
63
- One pure formatter remains responsible for the model prompt used by both executors. Interactive delivery must not shorten, summarize, or rebuild the model prompt from display fields.
64
-
65
- Before the step contract, the formatter adds the active settings scope, change number, bounded current value, allowed model paths, and exact `change-settings` action when that scope declares settings. It also shows the `queue-follow-up` and `remove-follow-up` actions. Actor identity is never part of model input. The extension derives it from the documented tool call.
95
+ An assistant-message step parks for its origin Pi session. A detached run without an approved origin session fails before it creates that message.
66
96
 
67
97
  ## Compact display
68
98
 
69
- The collapsed card shows only useful workflow identity and current work. For example:
99
+ A collapsed step card shows only the workflow identity and current work. For example:
70
100
 
71
101
  ```text
72
102
  ▶ monitor › check
73
103
  Checking the monitored target
74
104
  ```
75
105
 
76
- A reminder or resumed delivery adds a short label:
106
+ A reminder or resumed prompt adds a short label:
77
107
 
78
108
  ```text
79
109
  ↻ monitor › check · reminder
80
110
  Checking the monitored target
81
111
  ```
82
112
 
83
- The card uses the run title when it is more useful than the workflow name. It omits missing status detail instead of inventing one. Long fields are clipped or wrapped to the available terminal width.
113
+ The card uses the run title when it is more useful than the workflow name. It omits missing detail and clips or wraps long text to the terminal width.
84
114
 
85
- The expanded card shows:
115
+ The expanded card shows the workflow name, run title, run ID, node ID, attempt ID, step reason, completion form, expected output, optional character limit, and complete model prompt. Expansion uses Pi's standard custom-message state and keys. Pi Workflows does not store another expansion setting.
86
116
 
87
- - workflow name and run title
88
- - run id
89
- - node id
90
- - attempt id
91
- - delivery kind
92
- - completion form
93
- - expected output and optional character limit
94
- - full model prompt
117
+ Notifications keep the custom type `pi-workflows-notification` and use `triggerTurn: false`. Decisions and terminal results use their approved structured renderers. The message kind fixes each send policy; the coordinator cannot change it at run time.
95
118
 
96
- Expansion uses Pi's existing custom-message expansion state and keys. pi-workflows does not add another toggle or store separate expansion state.
119
+ ## One sender
97
120
 
98
- ## Reminders and resumes
121
+ The extension has one `WorkflowMessageCoordinator` for all message kinds. The host keeps one active coordinator connection and process-local epoch for each origin session. A replacement connection fences the old one, so two Pi processes cannot send for the same session.
99
122
 
100
- The existing bounded reminder behavior stays in place for submitted steps. A reminder uses the same custom message type and renderer. It keeps the contract and sets `kind: "reminder"`. Assistant-message steps do not nudge or retry after a visible response.
123
+ The coordinator follows this sequence:
101
124
 
102
- A resumed step uses `kind: "resume"` only when the executor must send the instructions again. An interrupted assistant-message step keeps its attempt id. If its matching prompt already has a completed assistant child on the active branch, the executor adopts that exact response instead of displaying it again. Stale attempts and responses from another branch remain invalid.
125
+ 1. After every host connection, wait for the complete origin-session view and report the active branch before any send or turn report.
126
+ 2. Wait until the host view names the next eligible pending message, Pi is idle, and Pi has no queued user input.
127
+ 3. Save the message ID in the coordinator's queued map.
128
+ 4. Search the active Pi branch for the same hidden message ID and report a matching entry.
129
+ 5. Recheck synchronously that Pi is idle, has no pending input, the message is absent, and this connection still owns the active session epoch.
130
+ 6. Call `pi.sendMessage()` with no `await` between that final check and call.
131
+ 7. Wait until the new Pi entry is visible.
132
+ 8. Report the active branch so the host saves its Pi entry ID and marks the message `sent`.
103
133
 
104
- ## Notifications
134
+ The coordinator sends only one workflow message at a time. A poll can find work, but it cannot send an ID already in its queued map.
105
135
 
106
- Workflow notifications keep the separate custom type `pi-workflows-notification`.
136
+ Pi can emit `agent_start` before the host saves the new Pi entry ID. The coordinator keeps that start and any matching end in its in-memory session map. It first marks the workflow message `sent`, then reports the saved turn events in order. It does not drop a turn event while host confirmation is in progress.
107
137
 
108
- Agent-step messages use `triggerTurn: true` because they ask the model to work. Notifications use `triggerTurn: false` because they report state to the user without asking for an assistant response.
138
+ A visible active-branch entry with the hidden ID is `sent`, even if the source lifecycle cancelled the message before the evidence arrived. Active-branch absence is usable only when the branch has no matching ID, Pi is idle, and Pi has no pending messages in the same observation. If Pi or the extension disappears after the send call, the message stays `pending`; reconnect reports the branch before another send. These rules do not prove cross-branch absence or exactly-once model execution.
109
139
 
110
- The two message types use the same delivery coordinator but keep separate send policies. The coordinator never changes whether a message starts a model turn.
140
+ After Pi, the extension, or the host restarts, branch reporting runs before any new send. It re-creates a message of the source's own kind only when the active branch has no entry for that source. A pending interaction gets one `step` with reason `resumed`; a pending decision gets one `decision`. Old incompatible workflow state is not reinterpreted.
111
141
 
112
- ## Session and persistence impact
142
+ ## Model-turn status
113
143
 
114
- Interactive step deliveries use `sendMessage` instead of `sendUserMessage`. Existing session entries remain readable and are not rewritten.
144
+ `agent_start` has no message payload. The extension binds it through the current origin-session view. Turn binding ignores branch membership; only branch reporting and re-presentation inspect the active branch:
115
145
 
116
- The custom prompt and visible assistant response are normal documented Pi session messages. pi-workflows adds no Pi session schema, private entry type, or separate persistent store. SQLite stores each settled Pi entry once. Small relational links connect the workflow attempt to its prompt, response, first, and last entries. The attempt keeps only the assistant digest receipt. It does not copy the prompt or visible response into another blob.
146
+ - the latest sent step is open while its interaction remains pending and its run is not paused;
147
+ - a terminal or follow-up message is open only until its first turn ends;
148
+ - decisions and notifications never open a turn.
117
149
 
118
- If the renderer is unavailable, Pi still retains the custom message content. pi-workflows does not add a fallback path that sends a duplicate user message.
150
+ A start against a closed message is rejected. Follow-up turns are reported for ordering but do not show the completed workflow as `running`.
119
151
 
120
- ## Public API boundary
152
+ The extension creates one workflow turn ID at `agent_start` and keeps it through the matching `agent_end` and host reconnect. It buffers starts and ends until the session view and message receipt are ready.
121
153
 
122
- This design uses the documented `pi.sendMessage()` and `pi.registerMessageRenderer()` APIs. The renderer uses Pi's standard `expanded` state.
154
+ At `agent_end`, it derives `completed`, `aborted`, or `error` from the documented assistant messages. It reads response-entry evidence from `ctx.sessionManager.getBranch()`; the entry ID can be null. The host saves one immutable end result. A repeated report adopts it, and a stale turn ID cannot clear newer activity. A supervised worker can continue after accepted tool output while that Pi turn is still open. This normal continuation leaves the session capture recording until `agent_end`; it does not mark the capture as interrupted.
123
155
 
124
- It does not require a Pi core change or private Pi API.
156
+ The host applies the end and its workflow consequence in one transaction. An aborted turn sets the run pause and cancels the request's pending step messages; the interaction derives its paused state from the run. Resuming that submitted-output step atomically clears the pause, increments the interaction revision, and creates one new step message with reason `resumed`. That message starts a fresh Pi model turn. A protected decision does not start a model turn, so its revision and decision message do not change when its run resumes. A completed, recoverably failed, or proved-lost turn increments `unproductiveTurnEnds` only when the submitted-output step is pending, not paused, and has no accepted or validating submission. Values one and two create one step message with reason `reminder`; a value above two fails the attempt. A partial unique index enforces at most one pending step message for the request, regardless of reason. Acceptance, pause, cancel, timeout, and branch re-presentation cancel all pending step messages. A cancelled message did not start a turn and does not increment the counter.
125
157
 
126
- The workflow package adds `assistantMessage()` as an `expectedOutput` value for the existing `agent` node. It adds no node type, graph action, Pi tool, private API, or message-rendering option.
158
+ There is no activity heartbeat, refresh lease, or sequence counter. The host shows `running` from the matching start until the matching end. Host startup never marks a Pi turn lost. On `session_start`, only an idle-session branch report can close an open sent message with synthetic stop reason `lost`. Polling and time alone cannot create a reminder.
127
159
 
128
- ## Validation and tests
160
+ ## Feature ownership
161
+
162
+ The workflow message stores only Pi send facts. Other records remain authoritative:
163
+
164
+ - interactive requests own step contracts, attempts, deadlines, validation, model submissions, and `unproductiveTurnEnds`;
165
+ - human decisions own choices, verified answers, expiry, and continuation;
166
+ - terminal runs own outcomes, reasons, restart lineage, and results;
167
+ - notification nodes own their node results;
168
+ - follow-up records own prompt source and authority;
169
+ - settings records own current values and accepted changes.
170
+
171
+ Submitted-output steps can use reminders. Assistant-message steps do not send a reminder after a visible response. An interrupted assistant-message step keeps its attempt ID and adopts a matching completed response from the active branch. A stale attempt or another branch remains invalid.
172
+
173
+ Each source event creates its workflow message in the same SQLite transaction. The message cannot exist without its source fact, and a source fact cannot require a Pi message without the matching record.
174
+
175
+ ## Restored behavior
176
+
177
+ The shared contract supports these features without separate send paths:
129
178
 
130
- Tests verify:
179
+ - at most two reminder-reason step messages after model turns end without a valid submission;
180
+ - one resumed-reason step message after a presented paused step resumes;
181
+ - one terminal result and recovery turn for the final outcome of each interactive continuation chain;
182
+ - safe restart with lineage, a limit of three, and repeated-failure protection;
183
+ - ordered follow-up prompts after successful completion, terminal turn end, and release of later workflow reservations;
184
+ - protected decisions in Pi and approved external channels;
185
+ - passive notifications that do not start model turns;
186
+ - terminal result retention while its message is pending or its first turn is open, and then for 60 seconds after that turn ends, in the widget and `piw`;
187
+ - conversation recording linked to workflow attempts.
131
188
 
132
- - interactive and RPC executors give the model the same complete prompt
133
- - one step message starts one model turn, even when Pi stays busy longer than the poll interval and delivery claim lease
134
- - collapsed rendering does not show the full prompt
135
- - expanded rendering shows the full prompt, settings scope and change number, allowed paths, and exact contract ids
136
- - long and missing display fields render safely
137
- - reminders and resumed deliveries keep the active attempt id
138
- - submitted steps still reject stale attempts after timeout or cancellation
139
- - assistant steps wait for `agent_settled` and capture only visible text
140
- - empty, failed, aborted, tool-only, and explicitly over-limit responses fail once
141
- - session replay restores the same custom prompt and adopts an existing response once
142
- - detached execution parks for the origin session or fails clearly when none exists
143
- - notifications still enter context without starting a model turn
144
- - no duplicate prompt or assistant response is sent
189
+ A terminal or follow-up message does not reopen the completed workflow. A slash-looking follow-up remains plain model input because `pi.sendMessage()` does not dispatch extension commands or expand prompt templates. External effects remain idempotent or explicitly ambiguous.
190
+
191
+ ## Session recording
192
+
193
+ The extension records workflow-related Pi events through a batched host client operation. It uses documented Pi events and does not read or edit Pi session files.
194
+
195
+ The host deduplicates settled entries by Pi entry ID. It links attempts to their prompt, response, first, and last entries. A recording failure does not fail workflow execution.
196
+
197
+ ## Public API boundary
198
+
199
+ The extension uses documented `pi.sendMessage()`, `pi.registerMessageRenderer()`, session lifecycle events, agent lifecycle events, widgets, status, commands, shortcuts, and session IDs.
200
+
201
+ This design does not change Pi core, use private Pi APIs, or change Pi session schemas. It does not add another database or runtime.
202
+
203
+ ## Validation and tests
145
204
 
146
- The end-to-end test inspects the provider-facing prompt as well as the TUI message record. A correct card with missing model instructions is a failure.
205
+ Tests must prove:
206
+
207
+ - every message kind uses the one coordinator;
208
+ - one workflow message ID creates at most one confirmed Pi entry and one automatic model turn;
209
+ - a later manual turn uses a new workflow turn ID without creating another Pi entry;
210
+ - two Pi processes that open one session cannot both send because one process-local coordinator epoch is active;
211
+ - restart recovery reports the branch and adopts an existing entry before it sends;
212
+ - branch absence is usable only when Pi is idle and has no pending input;
213
+ - a crash after send leaves the message pending and cannot cause a resend before branch reporting;
214
+ - messages remain in saved order without message-pointer deadlocks;
215
+ - an early `agent_start` and `agent_end` wait for the message receipt and session view, then apply in order;
216
+ - every matching model turn shows `running` for its full duration;
217
+ - normal worker continuation leaves the active session capture open until the matching turn ends;
218
+ - stale turn-end reports and starts against closed messages are rejected;
219
+ - a manual turn cancels pending step messages that it supersedes;
220
+ - a turn cannot bind to an interaction whose run is paused;
221
+ - aborted turns pause without incrementing the unproductive-turn counter;
222
+ - resuming an aborted step creates one new resumed message and one fresh model turn;
223
+ - resuming a protected decision keeps its answer revision and does not create a duplicate decision message;
224
+ - host restart does not close a live Pi turn, while an idle-session branch report can close an unended turn as lost;
225
+ - two reminder-reason steps are sent at most once and the next unproductive turn fails;
226
+ - initial, reminder, and resumed prompts use the same step kind and differ only by reason;
227
+ - terminal and follow-up messages each start only at their durable boundary;
228
+ - a branch switch creates one resumed-reason step only when that branch has no entry for the interaction;
229
+ - a missing protected decision creates another decision message, not a step;
230
+ - branch evidence changes a cancelled message to sent;
231
+ - a follow-up-started workflow blocks the next follow-up through its session reservation;
232
+ - notifications and protected decisions do not start model turns;
233
+ - the provider receives the complete step prompt but no workflow message ID or internal send state;
234
+ - collapsed and expanded cards remain safe and complete;
235
+ - session recording adopts each settled Pi entry once.
236
+
237
+ The real Pi end-to-end test must use a clean Pi home with only the packed pi-workflows extension. It must accept any provider and model supported by base Pi. `openai` and `openai-codex` are separate providers.
147
238
 
148
239
  ## Security
149
240
 
150
- Workflow prompts and expected-output descriptions may contain untrusted text. The renderer treats them as text, applies terminal-safe wrapping, and does not interpret control sequences or markup from workflow data.
241
+ Workflow prompts and expected-output descriptions can contain untrusted text. Renderers treat them as text, wrap them safely, and do not interpret workflow control sequences or markup.
151
242
 
152
- Collapsed cards avoid showing full prompts in the normal conversation view. Expanded content and session files still contain the complete prompt, so existing session privacy rules continue to apply.
243
+ Collapsed cards hide full prompts from the normal conversation view. Expanded cards and Pi session files still contain the full content, so normal session privacy rules apply. Credentials, internal send state, and internal message IDs do not enter provider-facing content.
@@ -159,7 +159,8 @@ workflow stores a long-lived registry token:
159
159
  For later versions:
160
160
 
161
161
  1. Update `version` in `package.json`, `package-lock.json`, `tui/Cargo.toml`,
162
- and `tui/Cargo.lock`, then merge that change into the default branch.
162
+ `tui/Cargo.lock`, and `herdr-plugin.toml`, then merge that change into the
163
+ default branch.
163
164
  2. Publish a GitHub Release whose tag is `v<version>`, such as `v0.2.0`.
164
165
  3. Wait for the **Publish npm package** and **Publish crates.io package**
165
166
  workflows to finish.