@osolmaz/pi-workflows 0.16.0 → 0.16.2
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.
- package/README.md +18 -2
- package/dist/channels/adapter-entry.d.ts +3 -0
- package/dist/channels/adapter-entry.js +203 -0
- package/dist/channels/adapter-entry.js.map +1 -0
- package/dist/channels/config.d.ts +43 -0
- package/dist/channels/config.js +234 -0
- package/dist/channels/config.js.map +1 -0
- package/dist/channels/protocol.d.ts +94 -0
- package/dist/channels/protocol.js +219 -0
- package/dist/channels/protocol.js.map +1 -0
- package/dist/channels/telegram.d.ts +46 -0
- package/dist/channels/telegram.js +285 -0
- package/dist/channels/telegram.js.map +1 -0
- package/dist/client/client.d.ts +1 -0
- package/dist/client/client.js +5 -1
- package/dist/client/client.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/protocol.d.ts +1 -1
- package/dist/client/protocol.js +11 -5
- package/dist/client/protocol.js.map +1 -1
- package/dist/client/view.d.ts +48 -16
- package/dist/client/view.js +1 -0
- package/dist/client/view.js.map +1 -1
- package/dist/controllers/index.d.ts +1 -1
- package/dist/controllers/index.js.map +1 -1
- package/dist/controllers/sqlite.d.ts +20 -165
- package/dist/controllers/sqlite.js +178 -436
- package/dist/controllers/sqlite.js.map +1 -1
- package/dist/extension/index.d.ts +23 -2
- package/dist/extension/index.js +637 -534
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/recorder.d.ts +95 -0
- package/dist/extension/recorder.js +530 -0
- package/dist/extension/recorder.js.map +1 -0
- package/dist/extension/remote-recorder-store.d.ts +25 -0
- package/dist/extension/remote-recorder-store.js +81 -0
- package/dist/extension/remote-recorder-store.js.map +1 -0
- package/dist/extension/session-events.d.ts +134 -0
- package/dist/extension/session-events.js +60 -0
- package/dist/extension/session-events.js.map +1 -0
- package/dist/extension/session-view.d.ts +2 -0
- package/dist/extension/session-view.js +60 -2
- package/dist/extension/session-view.js.map +1 -1
- package/dist/extension/step-message.d.ts +6 -4
- package/dist/extension/step-message.js +12 -6
- package/dist/extension/step-message.js.map +1 -1
- package/dist/extension/widget.d.ts +2 -2
- package/dist/extension/widget.js +28 -18
- package/dist/extension/widget.js.map +1 -1
- package/dist/extension/workflow-message-coordinator.d.ts +28 -0
- package/dist/extension/workflow-message-coordinator.js +346 -0
- package/dist/extension/workflow-message-coordinator.js.map +1 -0
- package/dist/host/channel-effects.d.ts +72 -0
- package/dist/host/channel-effects.js +271 -0
- package/dist/host/channel-effects.js.map +1 -0
- package/dist/host/channel-supervisor.d.ts +21 -0
- package/dist/host/channel-supervisor.js +54 -0
- package/dist/host/channel-supervisor.js.map +1 -0
- package/dist/host/runner.d.ts +50 -8
- package/dist/host/runner.js +1632 -341
- package/dist/host/runner.js.map +1 -1
- package/dist/host/state.d.ts +19 -16
- package/dist/host/state.js +251 -63
- package/dist/host/state.js.map +1 -1
- package/dist/host/view.d.ts +19 -9
- package/dist/host/view.js +227 -119
- package/dist/host/view.js.map +1 -1
- package/dist/host/worker-entry.d.ts +4 -1
- package/dist/host/worker-entry.js +23 -24
- package/dist/host/worker-entry.js.map +1 -1
- package/dist/host/worker-protocol.d.ts +15 -0
- package/dist/host/worker-protocol.js.map +1 -1
- package/dist/state/prune.js +3 -25
- package/dist/state/prune.js.map +1 -1
- package/dist/state/schema.js +76 -97
- package/dist/state/schema.js.map +1 -1
- package/dist/state/workflow-messages.d.ts +98 -0
- package/dist/state/workflow-messages.js +366 -0
- package/dist/state/workflow-messages.js.map +1 -0
- package/dist/viewer/render.js +1 -1
- package/dist/viewer/render.js.map +1 -1
- package/dist/viewer/tui.d.ts +1 -1
- package/dist/viewer/tui.js +65 -8
- package/dist/viewer/tui.js.map +1 -1
- package/dist/workflows/composition.js +0 -4
- package/dist/workflows/composition.js.map +1 -1
- package/dist/workflows/definition.js +0 -8
- package/dist/workflows/definition.js.map +1 -1
- package/dist/workflows/engine.js +4 -5
- package/dist/workflows/engine.js.map +1 -1
- package/dist/workflows/human-decision.d.ts +2 -0
- package/dist/workflows/human-decision.js +8 -2
- package/dist/workflows/human-decision.js.map +1 -1
- package/dist/workflows/index.d.ts +1 -1
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/schema.js +0 -4
- package/dist/workflows/schema.js.map +1 -1
- package/dist/workflows/settings.d.ts +1 -8
- package/dist/workflows/settings.js.map +1 -1
- package/dist/workflows/store.d.ts +20 -19
- package/dist/workflows/store.js +163 -354
- package/dist/workflows/store.js.map +1 -1
- package/dist/workflows/tool-input.d.ts +21 -0
- package/dist/workflows/tool-input.js +23 -1
- package/dist/workflows/tool-input.js.map +1 -1
- package/dist/workflows/types.d.ts +6 -8
- package/dist/workflows/workflow-message-content.d.ts +38 -0
- package/dist/workflows/workflow-message-content.js +157 -0
- package/dist/workflows/workflow-message-content.js.map +1 -0
- package/docs/2026-08-18-herdr-piw-plan.md +2 -1
- package/docs/2026-08-20-durable-workflow-launch-plan.md +1 -1
- package/docs/2026-08-25-workflow-follow-ups.md +26 -50
- package/docs/2026-08-30-out-of-process-workflow-host-plan.md +3 -3
- package/docs/2026-09-01-restore-session-delivery-controls-plan.md +4 -0
- package/docs/2026-09-01-unified-workflow-client-plan.md +5 -1
- package/docs/2026-09-02-installed-live-e2e-plan.md +15 -1
- package/docs/2026-09-02-unify-workflow-messages-plan.md +562 -0
- package/docs/2026-09-04-workflow-run-state-plan.md +363 -0
- package/docs/DEFERRED_TURNS.md +66 -294
- package/docs/HUMAN_DECISIONS.md +29 -30
- package/docs/HUMAN_DECISION_PRESENTATIONS.md +4 -11
- package/docs/SQLITE_STATE.md +25 -17
- package/docs/WORKFLOW_HOST.md +105 -31
- package/docs/WORKFLOW_STEP_MESSAGES.md +166 -75
- package/docs/live-replay-protocol.md +8 -4
- package/docs/tui-viewer.md +3 -1
- package/docs/workflows.md +44 -28
- package/herdr-plugin.toml +1 -1
- package/package.json +1 -1
- package/plugins/herdr/viewer.mjs +14 -5
- package/src/channels/adapter-entry.ts +220 -0
- package/src/channels/config.ts +296 -0
- package/src/channels/protocol.ts +333 -0
- package/src/channels/telegram.ts +335 -0
- package/src/client/client.ts +6 -2
- package/src/client/index.ts +2 -1
- package/src/client/protocol.ts +11 -5
- package/src/client/view.ts +51 -18
- package/src/controllers/index.ts +0 -1
- package/src/controllers/sqlite.ts +247 -845
- package/src/extension/index.ts +712 -647
- package/src/extension/recorder.ts +687 -0
- package/src/extension/remote-recorder-store.ts +126 -0
- package/src/extension/session-events.ts +119 -0
- package/src/extension/session-view.ts +86 -3
- package/src/extension/step-message.ts +19 -9
- package/src/extension/widget.ts +37 -17
- package/src/extension/workflow-message-coordinator.ts +398 -0
- package/src/host/channel-effects.ts +465 -0
- package/src/host/channel-supervisor.ts +73 -0
- package/src/host/runner.ts +2163 -525
- package/src/host/state.ts +333 -94
- package/src/host/view.ts +254 -122
- package/src/host/worker-entry.ts +38 -30
- package/src/host/worker-protocol.ts +11 -0
- package/src/state/prune.ts +3 -31
- package/src/state/schema.ts +76 -97
- package/src/state/workflow-messages.ts +599 -0
- package/src/viewer/render.ts +1 -5
- package/src/viewer/tui.ts +71 -6
- package/src/workflows/composition.ts +0 -5
- package/src/workflows/definition.ts +0 -8
- package/src/workflows/engine.ts +4 -6
- package/src/workflows/human-decision.ts +18 -2
- package/src/workflows/index.ts +0 -1
- package/src/workflows/schema.ts +0 -6
- package/src/workflows/settings.ts +1 -20
- package/src/workflows/store.ts +237 -489
- package/src/workflows/tool-input.ts +36 -1
- package/src/workflows/types.ts +6 -8
- package/src/workflows/workflow-message-content.ts +197 -0
- package/dist/extension/decision-channels.d.ts +0 -134
- package/dist/extension/decision-channels.js +0 -1307
- package/dist/extension/decision-channels.js.map +0 -1
- package/src/extension/decision-channels.ts +0 -1826
package/docs/DEFERRED_TURNS.md
CHANGED
|
@@ -1,338 +1,110 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Terminal workflow messages
|
|
2
2
|
|
|
3
|
-
This specification defines
|
|
3
|
+
This specification defines the final Pi message for an interactive workflow. It replaces the separate deferred-turn and `workflow_turn_intents` design. Terminal messages use the same host state, client operations, and extension coordinator as all other [workflow messages](WORKFLOW_STEP_MESSAGES.md).
|
|
4
4
|
|
|
5
|
-
The
|
|
5
|
+
The earlier [deferred-turn plan](plans/2026-08-21-deferred-turn-intents-plan.md) and [terminal restart plan](plans/2026-08-27-workflow-terminal-restart-plan.md) remain historical design records. This document is the current contract.
|
|
6
6
|
|
|
7
7
|
## Terms
|
|
8
8
|
|
|
9
|
-
- **
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
13
|
-
- **
|
|
14
|
-
- **Target session:** The Pi session that owns the successor turn.
|
|
9
|
+
- **Continuation chain:** One interactive workflow and the runs created to continue its checkpoints.
|
|
10
|
+
- **Final run:** The last run in the continuation chain.
|
|
11
|
+
- **Terminal outcome:** The final run's completed, failed, timed-out, or cancelled result.
|
|
12
|
+
- **Terminal workflow message:** The one model-facing message that reports the terminal outcome and offers valid next actions.
|
|
13
|
+
- **Restart:** A new immutable run created from an allowed action in the terminal turn.
|
|
15
14
|
|
|
16
15
|
## Core rule
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
The host creates one `terminal` workflow message in the same transaction that records the final terminal outcome. Only the final run in a continuation chain creates this message. A parent settled by a continuation does not create one.
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
2. a result presentation;
|
|
22
|
-
3. a factual fallback.
|
|
19
|
+
The terminal message uses the same `workflow_messages` table and `WorkflowMessageCoordinator` as steps, decisions, notifications, and follow-ups. Initial, reminder, and resumed prompts are one step-message kind. There is no `workflow_turn_intents` table, deferred-turn sender, terminal sender, or second send path.
|
|
23
20
|
|
|
24
|
-
|
|
21
|
+
The terminal message records the factual outcome before it starts a model turn. It does not keep the old assistant turn alive and does not delay cancellation or process cleanup.
|
|
25
22
|
|
|
26
|
-
|
|
23
|
+
## Outcomes
|
|
27
24
|
|
|
28
|
-
|
|
25
|
+
A terminal message can report:
|
|
29
26
|
|
|
30
|
-
|
|
27
|
+
- successful completion;
|
|
28
|
+
- workflow failure;
|
|
29
|
+
- timeout with no recovery edge;
|
|
30
|
+
- cancellation;
|
|
31
|
+
- launch or worker failure that became the final run outcome.
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
| -------- | ------------------------------------------------------------- | --------------------------------------------------------- |
|
|
34
|
-
| Pending | `resolvedAt` is null | No message path has resolved the intent. |
|
|
35
|
-
| Resolved | `resolvedAt`, `resolution`, and `resolutionMessageId` are set | One message path was sent or found in the session branch. |
|
|
33
|
+
Pause, Escape, a waiting checkpoint, a nonfinal continuation parent, user hold, and normal host shutdown do not create a terminal message.
|
|
36
34
|
|
|
37
|
-
|
|
35
|
+
A claim loss is a handoff. It creates no terminal outcome or terminal message unless later recovery proves that the run itself failed.
|
|
38
36
|
|
|
39
|
-
|
|
40
|
-
| ----------- | -------------------- | ---------------------------------------------------------------- |
|
|
41
|
-
| Ineligible | `eligibleAt` is null | A natural workflow prompt or presentation can still arrive. |
|
|
42
|
-
| Eligible | `eligibleAt` is set | Durable state shows that no immediate natural successor remains. |
|
|
37
|
+
## Message content and controls
|
|
43
38
|
|
|
44
|
-
A
|
|
39
|
+
The message names the outcome and the run. It contains only controls valid for that saved outcome. A successful final run can offer follow-up work. A failed or timed-out final run can offer an allowed restart. A cancelled run offers no restart. An ambiguous external effect remains parked and creates no terminal message until explicit recovery produces a real terminal outcome.
|
|
45
40
|
|
|
46
|
-
|
|
41
|
+
A restart creates a new run. It does not reopen or mutate the terminal run. It keeps the approved input and source identity, uses a stable action fingerprint, rejects duplicate activation, and stops after three restarts in one chain.
|
|
47
42
|
|
|
48
|
-
|
|
49
|
-
workflowPrompt | presentation | fallback
|
|
50
|
-
```
|
|
43
|
+
The terminal result remains visible in the origin-session view while its terminal workflow message is pending or its first model turn is open, and then for 60 seconds after that turn ends. This display retention gives no claim, queue reservation, or execution authority. A new run in that session replaces it immediately. A verified operator can also clear it through `sessionView.clearTerminal`, exposed by `/workflow clear` and `piw`.
|
|
51
44
|
|
|
52
|
-
|
|
45
|
+
## Turn tracking
|
|
53
46
|
|
|
54
|
-
|
|
47
|
+
A terminal workflow message is open only until its first model turn ends. The extension reports the matching `agent_start` and `agent_end` through `workflowTurn.report`.
|
|
55
48
|
|
|
56
|
-
|
|
49
|
+
The end report includes `stopReason: "completed"`, `"aborted"`, or `"error"`. Response-entry evidence comes from `ctx.sessionManager.getBranch()` after `agent_end` and can be null. A repeated report adopts the stored result. A stale turn ID cannot end a newer turn.
|
|
57
50
|
|
|
58
|
-
|
|
59
|
-
agentCancelled | timedOut | failed | launchFailed | controllerInterrupted | claimLost | terminal | cancelled
|
|
60
|
-
```
|
|
51
|
+
If Pi restarts after a terminal message was sent but before its end was reported, the extension's idle-session active-branch report records a synthetic end with `stopReason: "lost"`. Host restart alone does not close the turn. The terminal result remains durable and visible, but the host does not pretend that the model turn completed.
|
|
61
52
|
|
|
62
|
-
|
|
53
|
+
## Sending and recovery
|
|
63
54
|
|
|
64
|
-
|
|
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. |
|
|
55
|
+
After every host connection, the coordinator waits for the complete origin-session view and reports the active branch before it sends a workflow message or reports a model turn. The host gives the next eligible pending message only to the active coordinator epoch for that session. A replacement connection fences the old one.
|
|
78
56
|
|
|
79
|
-
|
|
57
|
+
The coordinator waits until Pi is idle and has no pending messages. It keeps the terminal workflow message ID in its in-memory queued map, checks the active branch, and reports a matching entry before any send. Otherwise, it performs one final synchronous check that Pi is idle, has no pending input, the message is absent, and its connection still owns the active epoch. It calls documented `pi.sendMessage()` without an `await` between that check and the call.
|
|
80
58
|
|
|
81
|
-
|
|
59
|
+
A matching hidden workflow message ID in the active branch proves that Pi accepted the message. The host records the matching Pi entry ID and changes the message to `sent`, even if its source cancelled it after the send. If the extension reloads, it reports the active branch and adopts that entry before another send.
|
|
82
60
|
|
|
83
|
-
|
|
61
|
+
Absence is usable only when all three facts are true:
|
|
84
62
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
- node ID or `$launch`;
|
|
89
|
-
- attempt ID when known;
|
|
90
|
-
- cause.
|
|
63
|
+
1. the active branch has no matching hidden workflow message ID;
|
|
64
|
+
2. `ctx.isIdle()` is true; and
|
|
65
|
+
3. `ctx.hasPendingMessages()` is false.
|
|
91
66
|
|
|
92
|
-
|
|
67
|
+
If Pi or the extension disappears after `pi.sendMessage()` but before the branch report, the message stays `pending`. A replacement extension reports the active branch before it sends. Documented Pi APIs do not prove cross-branch absence or exactly-once model execution. Pi Workflows guarantees its saved message identity, active-branch evidence, and recovery decisions. It does not claim more.
|
|
93
68
|
|
|
94
|
-
|
|
69
|
+
## Follow-ups
|
|
95
70
|
|
|
96
|
-
|
|
71
|
+
Successful completion can have ordered [follow-up prompts](2026-08-25-workflow-follow-ups.md). They use the same workflow-message coordinator.
|
|
97
72
|
|
|
98
|
-
|
|
73
|
+
A follow-up is eligible only after:
|
|
99
74
|
|
|
100
|
-
|
|
75
|
+
- the final run is successfully completed;
|
|
76
|
+
- its terminal workflow message has been sent;
|
|
77
|
+
- the terminal model turn has ended;
|
|
78
|
+
- every earlier follow-up is settled or cancelled; and
|
|
79
|
+
- no nonterminal run reserves the origin session.
|
|
101
80
|
|
|
102
|
-
|
|
103
|
-
| --------------------------- | ---- | -------------------------------------------- |
|
|
104
|
-
| `intent_id` | No | Deterministic primary key. |
|
|
105
|
-
| `source_event_id` | No | Stable source transition identity. |
|
|
106
|
-
| `run_id` | No | Related workflow run. |
|
|
107
|
-
| `workflow_ref` | No | Workflow identity used in messages. |
|
|
108
|
-
| `target_session_id` | No | Session that can receive the successor. |
|
|
109
|
-
| `cause` | No | Closed cause value. |
|
|
110
|
-
| `node_id` | Yes | Source node when known. |
|
|
111
|
-
| `attempt_id` | Yes | Source attempt when known. |
|
|
112
|
-
| `fallback_facts_json` | No | Bounded factual payload. |
|
|
113
|
-
| `requested_at` | No | Absolute ISO 8601 creation time. |
|
|
114
|
-
| `eligible_at` | Yes | Absolute ISO 8601 fallback eligibility time. |
|
|
115
|
-
| `resolved_at` | Yes | Absolute ISO 8601 resolution time. |
|
|
116
|
-
| `resolution` | Yes | Closed resolution value. |
|
|
117
|
-
| `resolution_message_id` | Yes | Stable message identity. |
|
|
118
|
-
| `delivery_claim_token` | Yes | Current claimant. |
|
|
119
|
-
| `delivery_claim_expires_at` | Yes | Claim expiry as epoch milliseconds. |
|
|
81
|
+
A follow-up turn is reported for ordering and recovery, but it does not make the completed workflow display as `running`.
|
|
120
82
|
|
|
121
|
-
|
|
83
|
+
## State and compatibility
|
|
122
84
|
|
|
123
|
-
|
|
124
|
-
- unresolved eligible intents by target session, eligibility time, and intent ID.
|
|
85
|
+
This is an alpha hard cut in `pi-workflows-state` schema version 1.
|
|
125
86
|
|
|
126
|
-
|
|
87
|
+
- `workflow_messages` owns terminal message content, `pending`, `sent`, or `cancelled` state, Pi entry evidence, and turn reports.
|
|
88
|
+
- Run and continuation rows own terminal outcomes and chain identity.
|
|
89
|
+
- `workflow_follow_ups` owns accepted follow-up source prompts and cancellation only.
|
|
90
|
+
- `workflow_turn_intents` and `workflow_follow_up_queues` do not exist.
|
|
127
91
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
`fallback_facts_json` uses this versioned camelCase object:
|
|
131
|
-
|
|
132
|
-
```json
|
|
133
|
-
{
|
|
134
|
-
"schema": "pi-workflows.deferred-turn-facts.v1",
|
|
135
|
-
"workflowName": "autoimplement",
|
|
136
|
-
"runId": "20260821T081731Z-autoimplement-407480dd",
|
|
137
|
-
"observedState": "failed",
|
|
138
|
-
"cause": "failed",
|
|
139
|
-
"nodeId": "$launch",
|
|
140
|
-
"attemptId": null,
|
|
141
|
-
"reason": "scope must be a non-empty string",
|
|
142
|
-
"handoff": false
|
|
143
|
-
}
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
Rules:
|
|
147
|
-
|
|
148
|
-
- `schema` is required and has the exact value shown above.
|
|
149
|
-
- `workflowName`, `runId`, `observedState`, and `cause` are required strings.
|
|
150
|
-
- `nodeId`, `attemptId`, and `reason` are strings or null.
|
|
151
|
-
- `handoff` is a required boolean.
|
|
152
|
-
- `reason` is safe diagnostic text with at most 8,192 characters.
|
|
153
|
-
- The serialized object is at most 64 KiB.
|
|
154
|
-
- Unknown fields are rejected during alpha.
|
|
155
|
-
- The object must not contain credentials, raw environment values, or unbounded command output.
|
|
156
|
-
|
|
157
|
-
`handoff: true` means another runner can continue. A handoff message must not describe the run as terminal unless separate durable state proves it.
|
|
158
|
-
|
|
159
|
-
## Store operations
|
|
160
|
-
|
|
161
|
-
The controller store provides these internal operations:
|
|
162
|
-
|
|
163
|
-
- `ensureWorkflowTurnIntent`
|
|
164
|
-
- `getWorkflowTurnIntent`
|
|
165
|
-
- `claimWorkflowTurnIntentForRun`
|
|
166
|
-
- `claimEligibleWorkflowTurnIntentsForSession`
|
|
167
|
-
- `makeWorkflowTurnIntentEligible`
|
|
168
|
-
- `resolveWorkflowTurnIntent`
|
|
169
|
-
- `releaseWorkflowTurnIntentClaim`
|
|
170
|
-
- a bounded diagnostic list operation
|
|
171
|
-
|
|
172
|
-
`ensureWorkflowTurnIntent` is idempotent when all immutable fields match. It fails on an identity collision.
|
|
173
|
-
|
|
174
|
-
Claim operations use a caller-supplied token and lease duration. Resolution requires the matching live claim token. Conditional updates ensure that natural delivery and fallback cannot both resolve the same intent.
|
|
175
|
-
|
|
176
|
-
List operations require a positive bounded limit and deterministic ordering.
|
|
177
|
-
|
|
178
|
-
## Abort ordering
|
|
179
|
-
|
|
180
|
-
For an eligible active-turn abort, Pi Workflows performs these steps in order:
|
|
181
|
-
|
|
182
|
-
1. Record abort provenance on the active run.
|
|
183
|
-
2. Build the source event and intent ID.
|
|
184
|
-
3. Attempt to persist the intent.
|
|
185
|
-
4. Record the intent ID in system-abort bookkeeping.
|
|
186
|
-
5. Call `ctx.abort()`.
|
|
187
|
-
|
|
188
|
-
The persistence attempt is synchronous because cancellation immediately crosses the turn boundary. A store failure does not prevent `ctx.abort()`. Terminal handling retries the same intent ID. If retry also fails, Pi Workflows reports that it could not preserve the successor-turn guarantee.
|
|
189
|
-
|
|
190
|
-
## Natural delivery
|
|
191
|
-
|
|
192
|
-
All extension-owned workflow prompts and result presentations pass through one `DeferredTurnCoordinator`.
|
|
193
|
-
|
|
194
|
-
If no intent exists, the coordinator preserves current message content and delivery options.
|
|
195
|
-
|
|
196
|
-
If a pending intent exists, the coordinator:
|
|
197
|
-
|
|
198
|
-
1. waits until the old turn has settled or the session is verified idle;
|
|
199
|
-
2. claims the intent for the run and session;
|
|
200
|
-
3. adds `turnIntentId` to the message details;
|
|
201
|
-
4. sends the normal prompt or presentation;
|
|
202
|
-
5. resolves the intent with the matching resolution and message ID;
|
|
203
|
-
6. releases the claim if sending fails.
|
|
204
|
-
|
|
205
|
-
A prompt generated during abort handling must not be sent into the aborting turn. The coordinator releases it after `agent_settled`.
|
|
206
|
-
|
|
207
|
-
## Fallback delivery
|
|
208
|
-
|
|
209
|
-
Fallback synchronization runs:
|
|
210
|
-
|
|
211
|
-
- after `agent_settled` and system-abort cleanup;
|
|
212
|
-
- when the target session starts;
|
|
213
|
-
- during the existing periodic synchronization pass while the session is idle.
|
|
214
|
-
|
|
215
|
-
The fallback custom message uses this details object:
|
|
216
|
-
|
|
217
|
-
```json
|
|
218
|
-
{
|
|
219
|
-
"schema": "pi-workflows.deferred-turn-message.v1",
|
|
220
|
-
"turnIntentId": "deferred-turn:...",
|
|
221
|
-
"runId": "20260821T081731Z-autoimplement-407480dd",
|
|
222
|
-
"cause": "failed",
|
|
223
|
-
"presentation": {
|
|
224
|
-
"workflowName": "autoimplement",
|
|
225
|
-
"state": "failed",
|
|
226
|
-
"reasonKind": "maxSteps",
|
|
227
|
-
"restart": {
|
|
228
|
-
"count": 0,
|
|
229
|
-
"limit": 3
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
`presentation` contains small, bounded display fields. `reasonKind` and `restart` are present only when the run records provide them. These fields do not replace or shorten the model prompt.
|
|
236
|
-
|
|
237
|
-
The message type is `pi-workflows-deferred-turn`. Delivery uses:
|
|
238
|
-
|
|
239
|
-
```ts
|
|
240
|
-
{
|
|
241
|
-
deliverAs: "followUp",
|
|
242
|
-
triggerTurn: true,
|
|
243
|
-
}
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
### Compact TUI card
|
|
247
|
-
|
|
248
|
-
Interactive Pi registers a custom renderer for `pi-workflows-deferred-turn`. The collapsed card shows the workflow name, state or cause, run identity, and restart count when it is available. It does not show the terminal facts JSON, exact input, result, fingerprint, or model instructions.
|
|
249
|
-
|
|
250
|
-
The renderer reads only the structured message details for its compact fields. It sanitizes workflow-derived text and uses Pi's standard TUI components, theme colors, and `expanded` state. A message with missing or invalid details renders as a safe generic workflow card.
|
|
251
|
-
|
|
252
|
-
Expanding the card shows the complete existing message content. The model and session history receive that same content whether the card is collapsed or expanded. Restored messages use the same renderer and do not create another entry or model turn.
|
|
253
|
-
|
|
254
|
-
This display behavior uses the documented `pi.sendMessage()` and `pi.registerMessageRenderer()` APIs. It does not change headless or RPC delivery. Presentation messages that already use `display: false` stay hidden. It adds no Pi core change, private API, database table, migration, store, or external resource.
|
|
255
|
-
|
|
256
|
-
For a terminal run, the complete content contains the workflow identity and revision, terminal run ID, exact stored input, bounded result, terminal state and reason, restart count, and earlier terminal outcomes in the chain. It tells the model to use the current conversation, prefer a safe restart for an unfinished task after a technical or temporary failure, and stop for completed work, cancellation, missing authority, a required user decision, or a repeated failure. Values from input and result are data, not instructions.
|
|
257
|
-
|
|
258
|
-
The content comes only from existing run and queue records. Pi owns conversation history. Pi Workflows does not identify, hash, copy, or store an original user message.
|
|
259
|
-
|
|
260
|
-
Fallback delivery is disabled during session shutdown and while a user-interrupted workflow is held.
|
|
261
|
-
|
|
262
|
-
## Selected launch and restart
|
|
263
|
-
|
|
264
|
-
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`.
|
|
265
|
-
|
|
266
|
-
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.
|
|
267
|
-
|
|
268
|
-
`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.
|
|
269
|
-
|
|
270
|
-
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.
|
|
271
|
-
|
|
272
|
-
## Delivery recovery
|
|
273
|
-
|
|
274
|
-
Every resolving message includes the intent ID in its custom-message details.
|
|
275
|
-
|
|
276
|
-
Before sending, the coordinator scans the current session branch for that ID. If the message is already present, the coordinator resolves the intent without sending again. This repairs a crash or SQLite failure that occurs after `sendMessage` succeeds but before resolution is stored.
|
|
277
|
-
|
|
278
|
-
Store leases prevent concurrent processes from sending the same resolution. Branch identity handles the remaining send-before-resolution window.
|
|
279
|
-
|
|
280
|
-
## Claim transfer
|
|
281
|
-
|
|
282
|
-
Claim loss stops the old runner's work and all fenced SQLite writes. The intent stays pending and fallback-ineligible while another runner can resume the run.
|
|
283
|
-
|
|
284
|
-
The new runner's next workflow prompt can resolve the intent. If durable state later proves a terminal outcome with no natural successor, terminal handling makes the intent eligible for fallback to its target session.
|
|
285
|
-
|
|
286
|
-
Time alone does not prove terminal failure.
|
|
287
|
-
|
|
288
|
-
## Launch notifications
|
|
289
|
-
|
|
290
|
-
Workflow-authored `progress` and `final` notifications remain passive. They do not trigger model turns.
|
|
291
|
-
|
|
292
|
-
The `launch_failure` notification kind is removed. Queued launch failure creates an eligible turn intent instead.
|
|
293
|
-
|
|
294
|
-
Pending `launch_failure` rows from the earlier alpha contract are incompatible. Pi Workflows must stop with a clear controller-store reset instruction. It must not reinterpret, migrate, or silently delete those rows.
|
|
295
|
-
|
|
296
|
-
## Post-completion follow-ups
|
|
297
|
-
|
|
298
|
-
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.
|
|
299
|
-
|
|
300
|
-
See [Continue normal work after a workflow finishes](2026-08-25-workflow-follow-ups.md).
|
|
301
|
-
|
|
302
|
-
## Availability limits
|
|
303
|
-
|
|
304
|
-
Pi Workflows can guarantee only the facts under its control:
|
|
305
|
-
|
|
306
|
-
- the intent was stored;
|
|
307
|
-
- a claimant acquired it;
|
|
308
|
-
- a custom message was sent or found in the session branch;
|
|
309
|
-
- the local intent was resolved.
|
|
310
|
-
|
|
311
|
-
Pi Workflows cannot guarantee model start or completion with the current Pi API. It also cannot deliver after permanent loss of the process, target session, controller store, machine, or model provider.
|
|
312
|
-
|
|
313
|
-
Undelivered intents remain pending. The implementation does not add a service to process them outside a live Pi session.
|
|
314
|
-
|
|
315
|
-
## Compatibility
|
|
316
|
-
|
|
317
|
-
This is an alpha hard cutover.
|
|
318
|
-
|
|
319
|
-
- Keep `pi-workflows.controller-store.v1`.
|
|
320
|
-
- Add `workflow_turn_intents` to the existing database.
|
|
321
|
-
- Remove the launch-trigger runtime path.
|
|
322
|
-
- Add no v2 schema, compatibility reader, dual write, alias, or feature flag.
|
|
323
|
-
- Keep historical terminal SQLite runs readable because their contract does not change.
|
|
92
|
+
The DDL digest changes in place. There is no migration, compatibility reader, dual path, fallback sender, alias, feature flag, or second schema. Incompatible state remains untouched and fails with the standard backup-and-reset instruction.
|
|
324
93
|
|
|
325
94
|
## Conformance
|
|
326
95
|
|
|
327
96
|
An implementation conforms when:
|
|
328
97
|
|
|
329
|
-
-
|
|
330
|
-
-
|
|
331
|
-
-
|
|
332
|
-
- a
|
|
333
|
-
-
|
|
334
|
-
-
|
|
335
|
-
-
|
|
336
|
-
-
|
|
337
|
-
-
|
|
338
|
-
-
|
|
98
|
+
- one continuation chain creates at most one terminal workflow message;
|
|
99
|
+
- only the final run creates that message;
|
|
100
|
+
- every terminal outcome uses the shared workflow-message path;
|
|
101
|
+
- a busy Pi session cannot cause duplicate terminal messages or model turns;
|
|
102
|
+
- reload reports the branch and adopts an existing active-branch entry before another send;
|
|
103
|
+
- unproved absence leaves the message `pending` and cannot cause a retry before branch reporting;
|
|
104
|
+
- a stale or disconnected coordinator epoch cannot send or report message state;
|
|
105
|
+
- active-branch evidence changes a matching pending or cancelled message to sent;
|
|
106
|
+
- an unended sent message receives a synthetic `lost` end only from an idle-session branch report after Pi restart;
|
|
107
|
+
- terminal status remains visible while its message is pending or its first turn is open, and then for 60 seconds after that turn ends, without retaining workflow authority;
|
|
108
|
+
- restart creates one separate run and never mutates the terminal run;
|
|
109
|
+
- follow-ups wait for the terminal turn and use the same coordinator; and
|
|
110
|
+
- no deferred-turn table, sender, or production fallback remains.
|
package/docs/HUMAN_DECISIONS.md
CHANGED
|
@@ -214,51 +214,49 @@ pi-workflows keeps credential references in a separate private file. A Telegram
|
|
|
214
214
|
}
|
|
215
215
|
```
|
|
216
216
|
|
|
217
|
-
Run `/workflow-channel setup` in Pi TUI to verify and install a profile, `/workflow-channel status` to inspect
|
|
217
|
+
Run `/workflow-channel setup` in Pi TUI to verify and install a profile, `/workflow-channel status` to inspect active profiles and uncertain operations, and `/workflow-channel reload` after a private configuration change. When status reports an uncertain operation, inspect Telegram before you run `/workflow-channel recover <message-id> confirm|retry`. `confirm` records that the operation happened. `retry` starts a new attempt and can duplicate the Telegram operation when the first result cannot be proved.
|
|
218
218
|
|
|
219
|
-
|
|
219
|
+
Setup asks for the token file path, not the token. It updates mode-`0600` private files and does not copy the token. Token values never enter source files, workflow inputs, SQLite runs, logs, child environments, or model-visible tool results.
|
|
220
|
+
|
|
221
|
+
The same Unix account can read a local credential file. This design prevents accidental propagation, not a hostile same-account process. A separately owned connector can implement the same channel-child protocol later if stronger isolation becomes necessary.
|
|
220
222
|
|
|
221
223
|
## Channel interface
|
|
222
224
|
|
|
223
|
-
|
|
225
|
+
The host owns decision state and launches each external channel adapter as a supervised child process. The private child protocol has these message kinds:
|
|
224
226
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
settle(decision: AcceptedHumanDecision | HumanDecisionCancellation): Promise<void>;
|
|
231
|
-
stop(): Promise<void>;
|
|
232
|
-
}
|
|
233
|
-
```
|
|
227
|
+
- `channel.ready`;
|
|
228
|
+
- `channel.present`;
|
|
229
|
+
- `channel.answer`;
|
|
230
|
+
- `channel.settle`; and
|
|
231
|
+
- `channel.exiting`.
|
|
234
232
|
|
|
235
|
-
The
|
|
233
|
+
Each message names the adapter epoch, channel profile, saved request or settlement record, expected revision, and stable attempt ID. The host validates and saves every state change. The child never opens SQLite, loads workflow code, changes a run directly, receives the canonical decision subject, or receives another channel's credentials.
|
|
236
234
|
|
|
237
|
-
Channel
|
|
235
|
+
Channel handling is independent from workflow routing. A failed Telegram send leaves the decision available in Pi. Audience policy decides whether one successful channel is enough or whether all configured channels must receive the request.
|
|
238
236
|
|
|
239
237
|
### Pi channel
|
|
240
238
|
|
|
241
|
-
The
|
|
239
|
+
The host creates one `decision` workflow message for the origin session. The shared extension coordinator shows it through documented `pi.sendMessage()` with no model turn. The message lists the request ID, choices, input rule, and deadline. It uses no blocking Pi dialog and no private Pi API.
|
|
242
240
|
|
|
243
|
-
|
|
241
|
+
A verified operator answers with `/workflow answer` or the matching `piw` control. The host validates the choice and optional text, records the Pi channel as the source, and accepts only the first valid winner. Closing or reloading Pi cannot lose the request. On `session_start` or `session_tree`, the extension adopts an existing decision message or creates one new `decision` message when the pending request has no entry on the active branch.
|
|
244
242
|
|
|
245
243
|
### Telegram channel
|
|
246
244
|
|
|
247
|
-
The Telegram
|
|
245
|
+
The Telegram adapter uses the Bot API and private profile configuration. It sends one decision message with inline buttons.
|
|
248
246
|
|
|
249
247
|
A choice without input submits from its button. A text choice such as `replan` works as follows:
|
|
250
248
|
|
|
251
249
|
1. the operator presses **Replan**;
|
|
252
250
|
2. the bot sends a `ForceReply` prompt tied to that decision and choice;
|
|
253
251
|
3. the operator replies to that exact prompt;
|
|
254
|
-
4. the
|
|
252
|
+
4. the adapter verifies the numeric user ID, chat ID, reply message ID, decision ID, and request digest; and
|
|
255
253
|
5. the exact received text becomes `input.instructions`.
|
|
256
254
|
|
|
257
|
-
The adapter does not infer a choice from ordinary chat text. Callback payloads contain short opaque IDs because Telegram limits callback data.
|
|
255
|
+
The adapter does not infer a choice from ordinary chat text. Callback payloads contain short opaque IDs because Telegram limits callback data. The host's channel records map each opaque ID to the validated decision request. Credentials remain outside the database and reach only the matching supervised adapter child.
|
|
258
256
|
|
|
259
|
-
Telegram permits one long-polling consumer for a bot profile.
|
|
257
|
+
Telegram permits one long-polling consumer for a bot profile. The global host starts at most one adapter child for that profile and keeps the package-owned on-demand host process alive while an external decision is pending. No Pi process or adapter child owns a SQLite lease. If the host stops, the next Pi, CLI, or `piw` client starts it and the host recovers the saved channel state before it starts another adapter child. This design installs no operating-system service.
|
|
260
258
|
|
|
261
|
-
The Bot API does not provide an idempotency key for `sendMessage`.
|
|
259
|
+
The Bot API does not provide an idempotency key for `sendMessage`. Before it tells the adapter to send or settle a message, the host records the exact attempt in `effects` and `effect_attempts`. A confirmed Telegram message ID settles the effect and remains in its result. A timed-out, disconnected, or interrupted exact attempt with no proof becomes `ambiguous` and is not retried automatically. Pi remains available while the operator checks Telegram and explicitly confirms or retries the operation. This prevents blind duplicate sends without claiming exactly-once Telegram behavior.
|
|
262
260
|
|
|
263
261
|
## Durable decision records
|
|
264
262
|
|
|
@@ -268,8 +266,8 @@ Human decisions use the canonical [SQLite state](SQLITE_STATE.md) database:
|
|
|
268
266
|
- `human_decision_submissions` records human, policy, channel, and control candidates;
|
|
269
267
|
- `human_decision_resolutions` stores the one accepted-or-cancelled winner;
|
|
270
268
|
- `continuations` links the parent and continuation runs;
|
|
271
|
-
- `effects`
|
|
272
|
-
-
|
|
269
|
+
- `effects` and `effect_attempts` record parent settlement, continuation, external delivery, and external settlement work; and
|
|
270
|
+
- `channels`, `channel_cursors`, and `channel_messages` store channel identity, polling position, and decision delivery or settlement receipts.
|
|
273
271
|
|
|
274
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.
|
|
275
273
|
|
|
@@ -315,7 +313,7 @@ Recovery follows these rules:
|
|
|
315
313
|
- duplicate channel updates are harmless;
|
|
316
314
|
- stale responses are rejected;
|
|
317
315
|
- one human or timeout response creates one continuation;
|
|
318
|
-
- the winning human answer or timeout policy
|
|
316
|
+
- the winning human answer or timeout policy settles or cancels the pending Pi decision workflow message;
|
|
319
317
|
- confirmed channel settlement is adopted without another remote call;
|
|
320
318
|
- failed channel settlement has a bounded retry count and cannot create an unbounded record loop; and
|
|
321
319
|
- cancellation resolves the owned waiting decision from durable state, even after restart, closes pending views, and prevents a later answer from continuing the run.
|
|
@@ -330,15 +328,15 @@ Adoption does not change the lease, claim generation, queue state, timestamps, o
|
|
|
330
328
|
|
|
331
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.
|
|
332
330
|
|
|
333
|
-
The engine remains independent from Pi and Telegram. Core code owns decision contracts
|
|
331
|
+
The engine remains independent from Pi and Telegram. Core code owns decision contracts and validation. The host 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.
|
|
334
332
|
|
|
335
333
|
## Contract impact
|
|
336
334
|
|
|
337
335
|
- **Session state:** Pi records normal workflow messages and interactive decision results.
|
|
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.
|
|
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.
|
|
339
337
|
- **Pi internals:** none.
|
|
340
|
-
- **Public Pi API:** documented extension lifecycle and
|
|
341
|
-
- **Public pi-workflows API:** typed human choices, `humanDecision().onTimeout`, `humanDecisionEdge()`,
|
|
338
|
+
- **Public Pi API:** documented extension lifecycle, message, command, session, and status APIs only.
|
|
339
|
+
- **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.
|
|
342
340
|
|
|
343
341
|
## Verification requirements
|
|
344
342
|
|
|
@@ -349,7 +347,8 @@ The implementation must test:
|
|
|
349
347
|
- runtime choice and input validation;
|
|
350
348
|
- legacy checkpoint continuation;
|
|
351
349
|
- model-tool answer rejection;
|
|
352
|
-
- Pi
|
|
350
|
+
- Pi decision workflow messages, branch adoption, branch-specific decision messages, and verified answers;
|
|
351
|
+
- supervised channel-child protocol fencing and restart recovery;
|
|
353
352
|
- Telegram callbacks and reply binding with a fake Bot API;
|
|
354
353
|
- unauthorized users and chats;
|
|
355
354
|
- stale request digests;
|
|
@@ -361,7 +360,7 @@ The implementation must test:
|
|
|
361
360
|
- identical and conflicting retries;
|
|
362
361
|
- crashes before and after answer acceptance and continuation creation;
|
|
363
362
|
- ambiguous Telegram sends;
|
|
364
|
-
- long-poll
|
|
363
|
+
- one supervised long-poll adapter per profile and host-restart recovery;
|
|
365
364
|
- decision cancellation and expiry;
|
|
366
365
|
- included `plan-approval` routes and bounded replan loops;
|
|
367
366
|
- viewer redaction; and
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Human decision presentations
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Human decision requests separate their canonical
|
|
4
4
|
subject from the complete readable message shown to an operator. Human decisions use
|
|
5
5
|
one v1 contract with no legacy body form or parallel schema version.
|
|
6
6
|
The implementation plan is in
|
|
@@ -216,20 +216,13 @@ The renderer:
|
|
|
216
216
|
|
|
217
217
|
The renderer never adds an ellipsis in place of omitted decision content.
|
|
218
218
|
|
|
219
|
-
The channel
|
|
220
|
-
unambiguous response. If a send result is ambiguous, it marks delivery unknown
|
|
221
|
-
and does not retry that part or later parts automatically. Another configured
|
|
222
|
-
channel can still answer the decision.
|
|
219
|
+
The host saves each channel message before the adapter sends it. It records each part after an unambiguous response. If a send result is uncertain, the host marks that channel message `ambiguous` and does not retry that part or later parts automatically. Another configured channel can still answer the decision.
|
|
223
220
|
|
|
224
221
|
### Pi
|
|
225
222
|
|
|
226
|
-
|
|
227
|
-
complete presentation and fingerprint together with the choices and optional
|
|
228
|
-
text prompt. It wraps and scrolls while responding to resize, theme, cancellation,
|
|
229
|
-
and `AbortSignal` events.
|
|
223
|
+
The host creates one `decision` workflow message. The shared extension coordinator sends it through documented `pi.sendMessage()` without starting a model turn. Its custom renderer shows the complete presentation and fingerprint with the choices, input rules, request ID, and deadline. It wraps and scrolls with normal Pi custom-message behavior.
|
|
230
224
|
|
|
231
|
-
When Telegram or another channel accepts the decision, the
|
|
232
|
-
dialog. pi-workflows does not modify Pi core or use undocumented TUI state.
|
|
225
|
+
A verified operator answers through `/workflow answer` or the matching `piw` control. When Telegram or another channel accepts the decision, the host cancels an unsent Pi decision message. A sent card remains normal conversation history, while later answer controls return the saved winner. Pi Workflows does not open a blocking dialog, modify Pi core, or use undocumented TUI state.
|
|
233
226
|
|
|
234
227
|
### Other channels
|
|
235
228
|
|