@osolmaz/pi-workflows 0.11.2 → 0.12.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.
- package/README.md +11 -7
- package/dist/builtins/autoimplement-command-batches.d.ts +52 -0
- package/dist/builtins/autoimplement-command-batches.js +258 -0
- package/dist/builtins/autoimplement-command-batches.js.map +1 -0
- package/dist/builtins/autoimplement.workflow.d.ts +29 -3
- package/dist/builtins/autoimplement.workflow.js +876 -231
- package/dist/builtins/autoimplement.workflow.js.map +1 -1
- package/dist/builtins/catalog.js +3 -1
- package/dist/builtins/catalog.js.map +1 -1
- package/dist/builtins/index.d.ts +2 -0
- package/dist/builtins/index.js +1 -0
- package/dist/builtins/index.js.map +1 -1
- package/dist/builtins/sanity-check-session.d.ts +17 -0
- package/dist/builtins/sanity-check-session.js +168 -0
- package/dist/builtins/sanity-check-session.js.map +1 -0
- package/dist/builtins/sanity-check.workflow.d.ts +83 -0
- package/dist/builtins/sanity-check.workflow.js +398 -0
- package/dist/builtins/sanity-check.workflow.js.map +1 -0
- package/dist/controllers/sqlite.d.ts +90 -2
- package/dist/controllers/sqlite.js +380 -5
- package/dist/controllers/sqlite.js.map +1 -1
- package/dist/extension/decision-channels.js +7 -7
- package/dist/extension/deferred-turn-coordinator.d.ts +32 -0
- package/dist/extension/deferred-turn-coordinator.js +143 -0
- package/dist/extension/deferred-turn-coordinator.js.map +1 -0
- package/dist/extension/deferred-turn.d.ts +44 -0
- package/dist/extension/deferred-turn.js +110 -0
- package/dist/extension/deferred-turn.js.map +1 -0
- package/dist/extension/index.js +286 -47
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/step-message.d.ts +1 -0
- package/dist/extension/step-message.js.map +1 -1
- package/dist/herdr/setup.js +4 -4
- package/dist/workflows/command-batch.d.ts +38 -0
- package/dist/workflows/command-batch.js +176 -0
- package/dist/workflows/command-batch.js.map +1 -0
- package/dist/workflows/engine.js +8 -4
- package/dist/workflows/engine.js.map +1 -1
- package/dist/workflows/index.d.ts +1 -0
- package/dist/workflows/index.js +1 -0
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/schema.js +2 -1
- package/dist/workflows/schema.js.map +1 -1
- package/dist/workflows/shell.d.ts +4 -0
- package/dist/workflows/shell.js +6 -0
- package/dist/workflows/shell.js.map +1 -1
- package/dist/workflows/store.js +3 -1
- package/dist/workflows/store.js.map +1 -1
- package/dist/workflows/types.d.ts +4 -3
- package/docs/2026-08-18-herdr-piw-plan.md +1 -1
- package/docs/2026-08-20-durable-workflow-launch-plan.md +19 -23
- package/docs/CONTROLLERS.md +2 -2
- package/docs/DEFERRED_TURNS.md +298 -0
- package/docs/DESIGN_PHILOSOPHY.md +1 -1
- package/docs/HUMAN_DECISIONS.md +7 -7
- package/docs/HUMAN_DECISION_PRESENTATIONS.md +3 -3
- package/docs/MONITOR.md +2 -2
- package/docs/WORKFLOW_COMPOSITION.md +2 -2
- package/docs/WORKFLOW_STEP_MESSAGES.md +5 -5
- package/docs/WORKFLOW_UPDATES.md +28 -5
- package/docs/plans/2026-08-04-controller-runtime-plan.md +1 -1
- package/docs/plans/2026-08-05-always-on-workflows-plan.md +1 -1
- package/docs/plans/2026-08-10-agent-managed-monitor-workflows-plan.md +9 -9
- package/docs/plans/2026-08-13-built-in-workflow-catalog-plan.md +1 -1
- package/docs/plans/2026-08-13-session-addressed-workflow-notifications-plan.md +1 -1
- package/docs/plans/2026-08-16-workflow-updates-plan.md +3 -3
- package/docs/plans/2026-08-17-bundled-skills-plan.md +6 -6
- package/docs/plans/2026-08-19-human-decision-gates-plan.md +5 -5
- package/docs/plans/2026-08-19-human-decision-presentations-plan.md +1 -1
- package/docs/plans/2026-08-19-provider-compatible-workflow-tool-schema-plan.md +1 -1
- package/docs/plans/2026-08-19-workflow-composition-plan.md +6 -6
- package/docs/plans/2026-08-20-autoimplement-blocker-challenge-plan.md +3 -3
- package/docs/plans/2026-08-20-bounded-command-batches-plan.md +206 -0
- package/docs/plans/2026-08-20-herdr-plugin-sync-plan.md +9 -9
- package/docs/plans/2026-08-21-autoimplement-timeout-fallback-plan.md +242 -0
- package/docs/plans/2026-08-21-deferred-turn-intents-plan.md +324 -0
- package/docs/plans/2026-08-21-sanity-check-plan.md +175 -0
- package/docs/run-bundles.md +3 -1
- package/docs/workflows.md +62 -19
- package/examples/workflows/command-batch.workflow.ts +50 -0
- package/examples/workflows/sanity-check.workflow.ts +1 -0
- package/herdr-plugin.toml +3 -3
- package/package.json +1 -1
- package/schemas/decision-presentation-v1.schema.json +1 -1
- package/schemas/human-decision-accepted-v1.schema.json +1 -1
- package/schemas/human-decision-accepted-v2.schema.json +1 -1
- package/schemas/human-decision-answer-attempt-v1.schema.json +1 -1
- package/schemas/human-decision-cancellation-v1.schema.json +1 -1
- package/schemas/human-decision-continuation-v1.schema.json +1 -1
- package/schemas/human-decision-delivery-v1.schema.json +1 -1
- package/schemas/human-decision-delivery-v2.schema.json +1 -1
- package/schemas/human-decision-receipt-v1.schema.json +1 -1
- package/schemas/human-decision-receipt-v2.schema.json +1 -1
- package/schemas/human-decision-request-v1.schema.json +1 -1
- package/schemas/human-decision-request-v2.schema.json +1 -1
- package/schemas/human-decision-resolution-v1.schema.json +1 -1
- package/schemas/human-decision-resolution-v2.schema.json +1 -1
- package/schemas/human-decision-settlement-v1.schema.json +1 -1
- package/skills/autodoc/SKILL.md +33 -2
- package/skills/autoimplement/SKILL.md +66 -12
- package/skills/autoplan/SKILL.md +26 -2
- package/skills/monitor/SKILL.md +35 -21
- package/skills/pi-workflows/SKILL.md +19 -5
- package/src/builtins/autoimplement-command-batches.ts +364 -0
- package/src/builtins/autoimplement.workflow.ts +1065 -261
- package/src/builtins/catalog.ts +3 -1
- package/src/builtins/index.ts +13 -0
- package/src/builtins/sanity-check-session.ts +205 -0
- package/src/builtins/sanity-check.workflow.ts +624 -0
- package/src/controllers/sqlite.ts +580 -7
- package/src/extension/decision-channels.ts +7 -7
- package/src/extension/deferred-turn-coordinator.ts +171 -0
- package/src/extension/deferred-turn.ts +166 -0
- package/src/extension/index.ts +359 -49
- package/src/extension/step-message.ts +1 -0
- package/src/herdr/setup.ts +4 -4
- package/src/workflows/command-batch.ts +254 -0
- package/src/workflows/engine.ts +8 -5
- package/src/workflows/index.ts +15 -0
- package/src/workflows/schema.ts +2 -1
- package/src/workflows/shell.ts +10 -0
- package/src/workflows/store.ts +3 -1
- package/src/workflows/types.ts +4 -3
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Add Autoimplement timeout fallback
|
|
3
|
+
author: Onur Solmaz <2453968+osolmaz@users.noreply.github.com>
|
|
4
|
+
date: 2026-08-21
|
|
5
|
+
status: implemented
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Add Autoimplement timeout fallback
|
|
9
|
+
|
|
10
|
+
Autoimplement must not exit only because one of its agent nodes reached a time limit. It must start one fallback step that inspects the work already done and chooses the safest existing workflow stage to run next.
|
|
11
|
+
|
|
12
|
+
The `implement` node can take a long time. Increase its limit from 1 hour to 8 hours. Also add `timeoutMs: null` to the public workflow API so other workflows can explicitly disable a node's wall-clock timeout when that is the correct policy.
|
|
13
|
+
|
|
14
|
+
Keep this graph fallback separate from deferred successor turns. The fallback starts after the timed-out turn has ended. It does not queue, revive, extend, or continue that turn.
|
|
15
|
+
|
|
16
|
+
## Current behavior
|
|
17
|
+
|
|
18
|
+
The current built-in Autoimplement workflow has these limits:
|
|
19
|
+
|
|
20
|
+
- `implement`: 1 hour;
|
|
21
|
+
- `finalizeDelivery`: 30 minutes;
|
|
22
|
+
- omitted node timeout: the engine default of 15 minutes.
|
|
23
|
+
|
|
24
|
+
A node timeout is recorded as `timed_out`. The engine can route this outcome through `$result.outcome`, but Autoimplement does not use that route. A timeout therefore ends the complete run.
|
|
25
|
+
|
|
26
|
+
Two run bundles show the problem:
|
|
27
|
+
|
|
28
|
+
- `20260820T180206Z-autoimplement-2df27615` timed out in `implement` after 3,600,000 ms.
|
|
29
|
+
- `20260820T212618Z-autoimplement-06fcf309` timed out in `finalizeDelivery` after 1,800,000 ms.
|
|
30
|
+
|
|
31
|
+
The bundles contain the timeout evidence. The missing part is a workflow route that lets an agent inspect the current state and choose what to do next.
|
|
32
|
+
|
|
33
|
+
## Requirements
|
|
34
|
+
|
|
35
|
+
### Timeout policy
|
|
36
|
+
|
|
37
|
+
Extend `WorkflowNodeCommon.timeoutMs` to accept:
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
number | null | ((context) => number | null | Promise<number | null>);
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The values have these meanings:
|
|
44
|
+
|
|
45
|
+
- omitted: use the engine's 15-minute default;
|
|
46
|
+
- positive number: use that wall-clock deadline;
|
|
47
|
+
- `null`: do not apply a wall-clock deadline.
|
|
48
|
+
|
|
49
|
+
A callback that returns `null` has the same meaning as a fixed `null`. The existing 30-second limit for resolving a timeout callback still applies.
|
|
50
|
+
|
|
51
|
+
A null timeout disables only elapsed-time expiry. Cancellation, pause, park, claim loss, host shutdown, late-submission rejection, active-attempt cleanup, and the node's `AbortSignal` keep their current behavior.
|
|
52
|
+
|
|
53
|
+
Reject zero, negative numbers, `NaN`, infinity, strings, and other invalid values.
|
|
54
|
+
|
|
55
|
+
### Autoimplement limits
|
|
56
|
+
|
|
57
|
+
Set the built-in Autoimplement limits as follows:
|
|
58
|
+
|
|
59
|
+
- `implement`: `8 * 60 * 60_000`;
|
|
60
|
+
- `finalizeDelivery`: keep `30 * 60_000`;
|
|
61
|
+
- all other explicit node limits: unchanged;
|
|
62
|
+
- omitted node limits: keep the 15-minute engine default;
|
|
63
|
+
- `maxSteps`: keep 240.
|
|
64
|
+
|
|
65
|
+
The new null policy is a public capability. Autoimplement does not use null for `implement`; it uses the explicit 8-hour limit.
|
|
66
|
+
|
|
67
|
+
### Timeout fallback
|
|
68
|
+
|
|
69
|
+
Add one shared read-only fallback agent node to Autoimplement. Route supported agent-node `timed_out` outcomes to this node.
|
|
70
|
+
|
|
71
|
+
The fallback receives:
|
|
72
|
+
|
|
73
|
+
- the timed-out node ID, attempt ID, error, and prompt result record;
|
|
74
|
+
- the approved plan and current task scope;
|
|
75
|
+
- accepted outputs from earlier workflow steps;
|
|
76
|
+
- the previous fallback results in the current run.
|
|
77
|
+
|
|
78
|
+
The fallback must inspect:
|
|
79
|
+
|
|
80
|
+
- the current repository worktree, branch, diff, and commits;
|
|
81
|
+
- the remote branch and pull request, when they exist;
|
|
82
|
+
- current review and CI state when they affect the next route;
|
|
83
|
+
- merge and final-report state when delivery may already be complete.
|
|
84
|
+
|
|
85
|
+
The fallback itself does not edit files, run a mutation, commit, push, open or update a pull request, post a comment, or merge. It only chooses the next workflow stage.
|
|
86
|
+
|
|
87
|
+
Its bounded output must contain:
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"route": "retry | verify | review | ci | deliver | replan | blocked",
|
|
92
|
+
"reason": "why this is the safest next stage",
|
|
93
|
+
"evidence": ["state inspected before choosing the route"]
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The routes have these meanings:
|
|
98
|
+
|
|
99
|
+
- `retry`: run the timed-out stage again because inspection shows that its work is incomplete;
|
|
100
|
+
- `verify`: continue at the required verification stage because implementation work is ready;
|
|
101
|
+
- `review`: continue at review because publication is complete for the current head;
|
|
102
|
+
- `ci`: inspect CI because review is complete and CI is the next open stage;
|
|
103
|
+
- `deliver`: run `finalizeDelivery` because delivery is the next open stage or its outcome is uncertain;
|
|
104
|
+
- `replan`: enter the existing `redesign` Autoplan include because current evidence invalidates the approved plan;
|
|
105
|
+
- `blocked`: stop because no safe route is available within the approved scope.
|
|
106
|
+
|
|
107
|
+
Validate routes against the timed-out source. The fallback must not skip required implementation, verification, review, CI, authorization, or delivery checks.
|
|
108
|
+
|
|
109
|
+
### Graph routing
|
|
110
|
+
|
|
111
|
+
Use the existing `$result.outcome` interface. For each supported Autoimplement agent node:
|
|
112
|
+
|
|
113
|
+
- `ok` follows its current success route;
|
|
114
|
+
- `timed_out` routes to the shared fallback;
|
|
115
|
+
- `failed` keeps normal failure behavior;
|
|
116
|
+
- `cancelled` remains terminal and never enters fallback.
|
|
117
|
+
|
|
118
|
+
The engine checks cancellation before outcome routing, so user cancellation stays immediate.
|
|
119
|
+
|
|
120
|
+
A workflow node can have only one outgoing edge. When a successful node currently routes on its output, add a small pure compute router after the `ok` outcome. Keep compute nodes free of external effects.
|
|
121
|
+
|
|
122
|
+
If an outcome switch needs an explicit `failed` case, route it to a pure failure node that throws the original persisted error. Do not turn ordinary failures into fallback events.
|
|
123
|
+
|
|
124
|
+
### Bounded fallback
|
|
125
|
+
|
|
126
|
+
Allow at most three fallback executions in one Autoimplement run. Count them from the durable step records, independently of `maxSteps`.
|
|
127
|
+
|
|
128
|
+
After the third fallback, a later supported timeout must finish with the normal blocked result and include the timeout history. Do not run a fourth fallback.
|
|
129
|
+
|
|
130
|
+
If the fallback node itself fails or times out, stop the run. Do not route the fallback back to itself.
|
|
131
|
+
|
|
132
|
+
### Repeat only missing work
|
|
133
|
+
|
|
134
|
+
A fallback can send the workflow back to a stage whose earlier attempt may have completed some effects. Consequential nodes must inspect their current state before they act again.
|
|
135
|
+
|
|
136
|
+
At minimum:
|
|
137
|
+
|
|
138
|
+
- implementation and fix steps inspect the current diff and commits before changing files;
|
|
139
|
+
- `publish` checks whether the head is already pushed and whether the matching pull request already exists;
|
|
140
|
+
- review reuses accepted review evidence only when it still matches the current head;
|
|
141
|
+
- `finalizeDelivery` checks whether the expected head is already merged and whether the final report already exists.
|
|
142
|
+
|
|
143
|
+
A repeated node performs only missing work. It must not create a duplicate commit, push, pull request, review, merge, or final report.
|
|
144
|
+
|
|
145
|
+
## Implementation
|
|
146
|
+
|
|
147
|
+
1. Update `src/workflows/types.ts` so fixed and computed node timeouts can be `null`.
|
|
148
|
+
2. Update `src/workflows/schema.ts` to accept null and reject other invalid timeout values.
|
|
149
|
+
3. Update `src/workflows/engine.ts` so a null timeout does not create an elapsed-time timer. Keep all abort and cleanup paths active.
|
|
150
|
+
4. Update `src/workflows/store.ts` and `WorkflowNodeSnapshot` so a fixed null is written as `"timeoutMs": null`. Continue to omit timeout callbacks from snapshots.
|
|
151
|
+
5. Set `implement.timeoutMs` to `8 * 60 * 60_000` in `src/builtins/autoimplement.workflow.ts`.
|
|
152
|
+
6. Add the fallback output parser, fallback count guard, read-only fallback agent, deterministic route helpers, and terminal limit result to Autoimplement.
|
|
153
|
+
7. Route supported `timed_out` outcomes to the fallback. Preserve success, failure, and cancellation behavior.
|
|
154
|
+
8. Update consequential-node prompts so repeated attempts inspect state and perform only missing work.
|
|
155
|
+
9. Increment the built-in Autoimplement revision from 5 to 6 in `src/builtins/catalog.ts`.
|
|
156
|
+
10. Update `docs/workflows.md` and `docs/run-bundles.md` when implementation ships so public documentation matches the code.
|
|
157
|
+
|
|
158
|
+
## Alpha cutover
|
|
159
|
+
|
|
160
|
+
Keep these identifiers unchanged:
|
|
161
|
+
|
|
162
|
+
- `pi-workflows.autoimplement.v1`;
|
|
163
|
+
- `pi-workflows.definition-snapshot.v1`;
|
|
164
|
+
- existing run-state, trace, and run-bundle v1 identifiers.
|
|
165
|
+
|
|
166
|
+
Change their alpha contracts in place. Do not add a v2 schema, compatibility reader, migration, alias, dual route, or feature flag.
|
|
167
|
+
|
|
168
|
+
Autoimplement revision 6 replaces revision 5. A revision-5 or older run must refuse resume through the existing source-change check. Reload or restart Pi to load the new built-in revision.
|
|
169
|
+
|
|
170
|
+
## Tests
|
|
171
|
+
|
|
172
|
+
Add tests for:
|
|
173
|
+
|
|
174
|
+
- fixed `timeoutMs: null`;
|
|
175
|
+
- a timeout callback that returns null;
|
|
176
|
+
- omitted and numeric timeout behavior remaining unchanged;
|
|
177
|
+
- invalid timeout values;
|
|
178
|
+
- a null-timeout node completing after a short test default would have expired;
|
|
179
|
+
- immediate cancellation of a null-timeout node;
|
|
180
|
+
- fixed null in definition snapshots and callback omission;
|
|
181
|
+
- Autoimplement's 8-hour `implement` limit and unchanged other limits;
|
|
182
|
+
- successful routes remaining unchanged;
|
|
183
|
+
- supported timeouts entering the fallback;
|
|
184
|
+
- failed nodes keeping normal failure behavior;
|
|
185
|
+
- cancellation bypassing fallback;
|
|
186
|
+
- retry, verify, review, CI, delivery, replan, and blocked fallback routes;
|
|
187
|
+
- rejection of a route that would skip a required stage;
|
|
188
|
+
- the three-fallback limit;
|
|
189
|
+
- fallback failure and timeout stopping without recursion;
|
|
190
|
+
- no duplicate push, pull request, merge, or final report after a repeated stage;
|
|
191
|
+
- revision 6 discovery and refusal to resume revision 5;
|
|
192
|
+
- non-destructive real-Pi execution through the installed package path.
|
|
193
|
+
|
|
194
|
+
Tests must use temporary directories and fake commands. They must not call a real model, mutate a real pull request, or write outside their temporary roots.
|
|
195
|
+
|
|
196
|
+
## Acceptance criteria
|
|
197
|
+
|
|
198
|
+
- Autoimplement `implement` can run for up to 8 hours.
|
|
199
|
+
- Workflows can explicitly choose no wall-clock node deadline with `timeoutMs: null`.
|
|
200
|
+
- A supported Autoimplement timeout starts the shared fallback instead of ending the run immediately.
|
|
201
|
+
- The fallback inspects current state and chooses an existing safe stage without mutation.
|
|
202
|
+
- Cancellation remains immediate and terminal.
|
|
203
|
+
- Ordinary failures keep their current terminal behavior.
|
|
204
|
+
- No run executes the fallback more than three times.
|
|
205
|
+
- Repeated stages perform only missing work.
|
|
206
|
+
- Deferred successor-turn behavior is unchanged and remains a separate design.
|
|
207
|
+
- Autoimplement revision 6 is a hard alpha replacement for revision 5.
|
|
208
|
+
- Documentation and tests match the shipped behavior.
|
|
209
|
+
|
|
210
|
+
## Verification
|
|
211
|
+
|
|
212
|
+
Run these checks before completion:
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
npm run check
|
|
216
|
+
npm run test:e2e
|
|
217
|
+
npx slophammer-ts@latest dry .
|
|
218
|
+
npx slophammer-ts@latest check . --only ts.dependency-boundaries-required
|
|
219
|
+
npx -y @simpledoc/simpledoc check
|
|
220
|
+
git diff --check
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Review the final diff to confirm that it preserves unrelated work on the current branch and does not mix deferred successor turns into timeout fallback.
|
|
224
|
+
|
|
225
|
+
## Boundaries
|
|
226
|
+
|
|
227
|
+
- Do not implement deferred successor turns as part of this work.
|
|
228
|
+
- Do not queue, revive, or extend a timed-out turn.
|
|
229
|
+
- Do not change Pi core or use undocumented Pi APIs.
|
|
230
|
+
- Do not add a service, controller, daemon, timeout registry, effect database, or new persistence location.
|
|
231
|
+
- Do not change external repository policy, CI configuration, provider APIs, or credentials.
|
|
232
|
+
- Do not recover explicit cancellation.
|
|
233
|
+
- Do not remove bounded limits from Autoimplement nodes.
|
|
234
|
+
- Do not preserve compatibility with older alpha workflow revisions.
|
|
235
|
+
|
|
236
|
+
## Contract impact
|
|
237
|
+
|
|
238
|
+
- **Session state:** normal workflow messages, fallback prompts, and tool results only.
|
|
239
|
+
- **Other persistent data:** fixed null timeout values and normal fallback node outputs in existing v1 run bundles.
|
|
240
|
+
- **Pi internals:** none.
|
|
241
|
+
- **Public Pi API:** existing documented extension APIs only.
|
|
242
|
+
- **Public pi-workflows API:** `timeoutMs` gains explicit null support; existing agent, compute, and `$result.outcome` routing remain in use.
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Guarantee one successor turn after workflow interruption
|
|
3
|
+
author: Onur Solmaz <2453968+osolmaz@users.noreply.github.com>
|
|
4
|
+
date: 2026-08-21
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Guarantee one successor turn after workflow interruption
|
|
8
|
+
|
|
9
|
+
Pi Workflows can stop the active agent turn before the agent receives a tool result or can take its next action. This happens when an active workflow step is cancelled, times out, loses its queue claim, or is interrupted by a controller. The workflow can also return a successful start result and then fail asynchronously before it sends its first prompt. In that case, the user can see a UI error while the model still believes that the workflow started successfully.
|
|
10
|
+
|
|
11
|
+
Add one general rule: an eligible workflow event creates one durable obligation for one later agent turn. The next normal workflow prompt or result presentation satisfies the obligation when one exists. Otherwise, Pi Workflows sends one factual fallback turn after the old turn settles.
|
|
12
|
+
|
|
13
|
+
This plan replaces the earlier corrective-notification idea. A corrective message is one possible fallback. The underlying mechanism is a purpose-neutral successor-turn obligation.
|
|
14
|
+
|
|
15
|
+
Related documents:
|
|
16
|
+
|
|
17
|
+
- [Deferred workflow turns](../DEFERRED_TURNS.md)
|
|
18
|
+
- [Make deferred workflow launches durable](../2026-08-20-durable-workflow-launch-plan.md)
|
|
19
|
+
- [Route workflow reports to their starting session](2026-08-13-session-addressed-workflow-notifications-plan.md)
|
|
20
|
+
- [Workflow authoring reference](../workflows.md)
|
|
21
|
+
|
|
22
|
+
## Goals
|
|
23
|
+
|
|
24
|
+
- Keep workflow cancellation and process termination immediate.
|
|
25
|
+
- Give an agent that causes its own turn to stop one later normal turn when the Pi session remains available.
|
|
26
|
+
- Give asynchronous workflow failures one model-facing turn when the start tool already returned success.
|
|
27
|
+
- Let the next workflow prompt or presentation satisfy the obligation instead of sending a duplicate fallback.
|
|
28
|
+
- Preserve the obligation across ordinary extension restart, polling, and delivery races.
|
|
29
|
+
- Keep user Escape, pause, direct administrative cancellation, and session shutdown quiet.
|
|
30
|
+
- Keep workflow recovery separate from turn delivery.
|
|
31
|
+
|
|
32
|
+
## Non-goals
|
|
33
|
+
|
|
34
|
+
- Do not keep the aborted assistant turn alive.
|
|
35
|
+
- Do not return the cancel tool result to the aborted turn.
|
|
36
|
+
- Do not retry a workflow node, resume a terminal run, or repeat an external mutation.
|
|
37
|
+
- Do not add a workflow-engine primitive or a public workflow-author API.
|
|
38
|
+
- Do not add a service, another database file, an external queue, or a controller.
|
|
39
|
+
- Do not change Pi core or use a private Pi API.
|
|
40
|
+
- Do not guarantee a turn after permanent process, session, machine, storage, or model-provider loss.
|
|
41
|
+
- Do not treat message insertion as proof that the model started or completed a turn.
|
|
42
|
+
- Do not include the separate Autoimplement node-timeout recovery change in this implementation.
|
|
43
|
+
|
|
44
|
+
## Successor-turn rule
|
|
45
|
+
|
|
46
|
+
A source event can create at most one `DeferredTurnIntent`. The intent starts pending and resolves once as one of:
|
|
47
|
+
|
|
48
|
+
- `workflowPrompt`: the next workflow agent prompt was sent;
|
|
49
|
+
- `presentation`: a completed or waiting result presentation was sent;
|
|
50
|
+
- `fallback`: Pi Workflows sent a factual fallback message because no natural successor remained.
|
|
51
|
+
|
|
52
|
+
The three paths compete for the same record. A successful claim by one path prevents the other paths from sending another turn.
|
|
53
|
+
|
|
54
|
+
Fallback eligibility is separate from pending state. An intent can remain pending but ineligible while the workflow can still route to another agent step or presentation. Durable terminal or handoff evidence makes the intent eligible only when no immediate natural successor remains.
|
|
55
|
+
|
|
56
|
+
## Event policy
|
|
57
|
+
|
|
58
|
+
| Event | Create an intent | Initial fallback eligibility | Notes |
|
|
59
|
+
| ------------------------------------------------------------ | ---------------- | --------------------------------- | --------------------------------------------------------------------------------------------------------- |
|
|
60
|
+
| Agent calls `workflow cancel` during an active workflow turn | Yes | No | Persist before `ctx.abort()`. Terminal cancellation makes it eligible. |
|
|
61
|
+
| Agent-step timeout | Yes | No | A later recovery prompt can resolve it naturally. Terminal timeout makes it eligible. |
|
|
62
|
+
| Terminal workflow failure after an active turn abort | Yes | No | Terminal handling updates the existing intent and makes it eligible. |
|
|
63
|
+
| Workflow reports started, then crashes asynchronously | Yes | Yes, after durable failure | Covers failures before the first workflow prompt, including invalid runtime input discovered after start. |
|
|
64
|
+
| Queued launch activation fails | Yes | Yes, after durable launch failure | Replaces the `launch_failure` notification trigger. |
|
|
65
|
+
| Controller stops an active workflow agent turn | Yes | No | Terminal or handoff policy decides eligibility. |
|
|
66
|
+
| Queue claim is lost during an active agent turn | Yes | No | Treat as ownership transfer, not failure. |
|
|
67
|
+
| Completed or waiting run has a presentation | No new intent | Not applicable | The presentation resolves an existing intent when present. |
|
|
68
|
+
| Direct `/workflow cancel` | No | Not applicable | The user explicitly requested control and no automatic model turn. |
|
|
69
|
+
| Ordinary workflow pause | No | Not applicable | The current step stops only at its normal boundary. |
|
|
70
|
+
| User Escape | No | Not applicable | Keep the workflow held until explicit resume. |
|
|
71
|
+
| Session shutdown | No | Not applicable | A closing session cannot run another turn. |
|
|
72
|
+
|
|
73
|
+
A later distinct event can create a new intent. Repeated handling of the same source event must return the existing intent.
|
|
74
|
+
|
|
75
|
+
## Storage
|
|
76
|
+
|
|
77
|
+
Add `workflow_turn_intents` to the existing `SqliteControllerStore` database. Keep the database path and `pi-workflows.controller-store.v1` schema identifier.
|
|
78
|
+
|
|
79
|
+
The existing `workflow_notifications` table remains a passive outbox for workflow-authored `progress` and `final` reports. It cannot represent this lifecycle clearly because it has one delivery transition and identifies node notification output. Adding natural resolution, fallback eligibility, and competing claims to that table would mix two different contracts.
|
|
80
|
+
|
|
81
|
+
The intent table stores:
|
|
82
|
+
|
|
83
|
+
| Field | Meaning |
|
|
84
|
+
| --------------------------- | -------------------------------------------------------------------------------------------- |
|
|
85
|
+
| `intent_id` | Deterministic primary key |
|
|
86
|
+
| `source_event_id` | Stable identity of the event that requires a successor |
|
|
87
|
+
| `run_id` | Related workflow run |
|
|
88
|
+
| `workflow_ref` | Workflow identity used in factual messages |
|
|
89
|
+
| `target_session_id` | Pi session that owns the successor turn |
|
|
90
|
+
| `cause` | Closed event cause |
|
|
91
|
+
| `node_id` | Source node when known |
|
|
92
|
+
| `attempt_id` | Source attempt when known |
|
|
93
|
+
| `fallback_facts_json` | Bounded `pi-workflows.deferred-turn-facts.v1` camelCase facts used to build fallback content |
|
|
94
|
+
| `requested_at` | Time the obligation was created |
|
|
95
|
+
| `eligible_at` | Time fallback became eligible, or null |
|
|
96
|
+
| `resolved_at` | Time one path resolved the intent, or null |
|
|
97
|
+
| `resolution` | `workflowPrompt`, `presentation`, `fallback`, or null |
|
|
98
|
+
| `resolution_message_id` | Stable custom-message identity for the resolving send |
|
|
99
|
+
| `delivery_claim_token` | Current resolution claimant |
|
|
100
|
+
| `delivery_claim_expires_at` | Claim lease expiry |
|
|
101
|
+
|
|
102
|
+
Causes are:
|
|
103
|
+
|
|
104
|
+
```text
|
|
105
|
+
agentCancelled | timedOut | failed | launchFailed | controllerInterrupted | claimLost
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The deterministic intent ID includes the target session, run, source event, source node or `$launch`, attempt when known, and cause. Reusing an ID with different immutable facts is an error.
|
|
109
|
+
|
|
110
|
+
Store methods must support:
|
|
111
|
+
|
|
112
|
+
- idempotent intent creation;
|
|
113
|
+
- exact lookup;
|
|
114
|
+
- a natural-resolution claim for one run and session;
|
|
115
|
+
- an eligible fallback claim for one session;
|
|
116
|
+
- fallback eligibility updates;
|
|
117
|
+
- resolution with the matching claim token;
|
|
118
|
+
- claim release and lease expiry;
|
|
119
|
+
- bounded diagnostic listing.
|
|
120
|
+
|
|
121
|
+
Every claim and resolution uses an immediate transaction or conditional update.
|
|
122
|
+
|
|
123
|
+
## Abort handling
|
|
124
|
+
|
|
125
|
+
Track abort provenance on the active run before cancellation occurs. The provenance distinguishes:
|
|
126
|
+
|
|
127
|
+
- agent tool cancellation;
|
|
128
|
+
- direct user cancellation;
|
|
129
|
+
- timeout;
|
|
130
|
+
- controller interruption;
|
|
131
|
+
- claim loss;
|
|
132
|
+
- Escape;
|
|
133
|
+
- shutdown.
|
|
134
|
+
|
|
135
|
+
When the executor abort callback is about to call `ctx.abort()` for an eligible active turn:
|
|
136
|
+
|
|
137
|
+
1. Build the deterministic source event and intent ID.
|
|
138
|
+
2. Call `ensureWorkflowTurnIntent` synchronously.
|
|
139
|
+
3. Record the intent ID in system-abort bookkeeping.
|
|
140
|
+
4. Call `ctx.abort()` immediately.
|
|
141
|
+
|
|
142
|
+
A storage error must not prevent cancellation. Save a bounded error and retry the same intent during terminal handling. If both writes fail, show a clear warning and record a run event. Do not claim that a successor turn is guaranteed.
|
|
143
|
+
|
|
144
|
+
## Turn coordinator
|
|
145
|
+
|
|
146
|
+
Add one `DeferredTurnCoordinator` in `src/extension`. All extension-owned workflow agent prompts and result presentations pass through it.
|
|
147
|
+
|
|
148
|
+
When no intent exists, the coordinator preserves current message content and delivery options.
|
|
149
|
+
|
|
150
|
+
When an intent exists, the coordinator:
|
|
151
|
+
|
|
152
|
+
1. Defers the Pi send while the old turn is still active or aborting.
|
|
153
|
+
2. Waits for `agent_settled` or verified idle session state.
|
|
154
|
+
3. Claims the pending intent for natural resolution.
|
|
155
|
+
4. Adds `turnIntentId` to custom-message details.
|
|
156
|
+
5. Sends the normal workflow prompt or presentation.
|
|
157
|
+
6. Resolves the intent as `workflowPrompt` or `presentation`.
|
|
158
|
+
7. Releases the claim if sending fails.
|
|
159
|
+
|
|
160
|
+
This settlement rule applies to natural prompts as well as fallback messages. A recovery prompt sent while the old turn is aborting can otherwise be lost with that turn.
|
|
161
|
+
|
|
162
|
+
If the message send succeeds but SQLite resolution fails, later recovery scans the current session branch for `turnIntentId`. An existing message resolves the intent without another send.
|
|
163
|
+
|
|
164
|
+
## Terminal and asynchronous failures
|
|
165
|
+
|
|
166
|
+
Terminal handling uses the durable run or launch state as its source of truth.
|
|
167
|
+
|
|
168
|
+
For `cancelled`, `timed_out`, or `failed` outcomes with no natural successor:
|
|
169
|
+
|
|
170
|
+
1. Find or recreate the deterministic intent.
|
|
171
|
+
2. Update its bounded fallback facts from the observed terminal state.
|
|
172
|
+
3. Make it fallback-eligible.
|
|
173
|
+
|
|
174
|
+
A workflow can also fail after `workflow start` returns success but before an active agent step calls `ctx.abort()`. The run completion rejection path must create an already-eligible intent for this case. The current turn can finish normally, and the fallback arrives afterward with the actual failure. This prevents the model from reporting that a crashed workflow is still running.
|
|
175
|
+
|
|
176
|
+
Completed and waiting outcomes continue through normal presentation. That presentation resolves an existing intent and prevents fallback.
|
|
177
|
+
|
|
178
|
+
Parked runs remain pending for resume and do not claim a terminal outcome.
|
|
179
|
+
|
|
180
|
+
## Fallback delivery
|
|
181
|
+
|
|
182
|
+
The coordinator checks eligible intents:
|
|
183
|
+
|
|
184
|
+
- after `agent_settled`, after system-abort bookkeeping is clear;
|
|
185
|
+
- when a session starts;
|
|
186
|
+
- during the existing periodic synchronization pass while the session is idle.
|
|
187
|
+
|
|
188
|
+
For each claimed intent, it sends one `pi-workflows-deferred-turn` custom message with:
|
|
189
|
+
|
|
190
|
+
- the stable `turnIntentId`;
|
|
191
|
+
- the workflow and run identity;
|
|
192
|
+
- the observed terminal or handoff state;
|
|
193
|
+
- the source node and attempt when known;
|
|
194
|
+
- a bounded safe reason;
|
|
195
|
+
- an instruction to inspect durable state before any authorized correction.
|
|
196
|
+
|
|
197
|
+
Delivery uses:
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
{
|
|
201
|
+
deliverAs: "followUp",
|
|
202
|
+
triggerTurn: true,
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
The message does not say that recovery ran or succeeded. It does not resume the old run or grant permission for new work.
|
|
207
|
+
|
|
208
|
+
The coordinator does not deliver while the session is shutting down or while an Escape-interrupted workflow is held.
|
|
209
|
+
|
|
210
|
+
## Claim loss
|
|
211
|
+
|
|
212
|
+
Claim loss transfers execution responsibility. It is not proof that the run failed.
|
|
213
|
+
|
|
214
|
+
The old runner must:
|
|
215
|
+
|
|
216
|
+
1. Ensure the pending intent before aborting the active turn when possible.
|
|
217
|
+
2. Stop all fenced run-bundle writes.
|
|
218
|
+
3. Leave the intent unresolved and fallback-ineligible while another runner can continue.
|
|
219
|
+
|
|
220
|
+
The new runner's next workflow prompt can claim and resolve the same intent naturally. Only durable terminal state or explicit no-successor handoff evidence can make the intent fallback-eligible. A timer alone cannot establish failure.
|
|
221
|
+
|
|
222
|
+
## Launch failure change
|
|
223
|
+
|
|
224
|
+
Queued launch activation failure moves from `workflow_notifications` to deferred-turn intents.
|
|
225
|
+
|
|
226
|
+
After the queue row is durably marked failed:
|
|
227
|
+
|
|
228
|
+
1. Record the launch failure event.
|
|
229
|
+
2. Create one already-eligible intent keyed by the run and `$launch`.
|
|
230
|
+
3. Let post-settlement synchronization deliver the fallback.
|
|
231
|
+
|
|
232
|
+
Immediate `workflow start` validation errors remain ordinary tool errors in the current live turn and create no intent.
|
|
233
|
+
|
|
234
|
+
Remove the `launch_failure` runtime notification kind and trigger branch. Existing pending `launch_failure` rows are incompatible alpha state. Detect them before synchronization and give a precise controller-store reset instruction. Do not reinterpret, migrate, or silently delete them.
|
|
235
|
+
|
|
236
|
+
## Implementation order
|
|
237
|
+
|
|
238
|
+
1. Add the intent types, validation, row mapping, schema, and store operations.
|
|
239
|
+
2. Add storage tests for identity, eligibility, claims, leases, resolution, and collisions.
|
|
240
|
+
3. Add the event policy, stable identity helper, and fallback builder.
|
|
241
|
+
4. Add abort provenance to active-run control paths.
|
|
242
|
+
5. Persist intents before eligible `ctx.abort()` calls and retry during terminal handling.
|
|
243
|
+
6. Add the coordinator and route workflow prompts and presentations through it.
|
|
244
|
+
7. Add terminal eligibility and asynchronous post-start failure handling.
|
|
245
|
+
8. Move queued launch failure to intents and remove its notification trigger.
|
|
246
|
+
9. Add post-settlement fallback synchronization.
|
|
247
|
+
10. Add claim-loss transfer behavior.
|
|
248
|
+
11. Apply the alpha storage checks and reset error.
|
|
249
|
+
12. Update the workflow documentation to match the shipped behavior.
|
|
250
|
+
13. Run focused tests, real-Pi end-to-end tests, and all repository checks.
|
|
251
|
+
|
|
252
|
+
## Acceptance criteria
|
|
253
|
+
|
|
254
|
+
- Agent-issued active cancellation aborts the old turn and produces exactly one later fallback turn.
|
|
255
|
+
- The aborted turn does not receive the cancel result and does not continue work.
|
|
256
|
+
- A timeout with a natural recovery prompt resolves the intent through that prompt and sends no fallback.
|
|
257
|
+
- A terminal timeout sends one fallback after settlement.
|
|
258
|
+
- A workflow that returns a successful start result and then crashes sends one later model-facing failure turn.
|
|
259
|
+
- A completed or waiting presentation resolves an existing intent and sends no fallback.
|
|
260
|
+
- Queued launch failure uses an intent instead of a triggered workflow notification.
|
|
261
|
+
- Direct cancellation, pause, Escape, user hold, and shutdown send no automatic successor turn.
|
|
262
|
+
- Claim loss performs no stale fenced write and can resolve through the new runner's prompt.
|
|
263
|
+
- Polling, extension restart, lease expiry, and repeated terminal handling do not duplicate turns.
|
|
264
|
+
- A send-before-resolution crash is repaired from the session message identity.
|
|
265
|
+
- Permanent intent-store failure is reported without weakening cancellation or claiming delivery.
|
|
266
|
+
- Existing passive progress and final notifications keep their current behavior.
|
|
267
|
+
|
|
268
|
+
## Tests
|
|
269
|
+
|
|
270
|
+
Add or extend tests for:
|
|
271
|
+
|
|
272
|
+
- persisted type validation and JSON round trips;
|
|
273
|
+
- deterministic identity and collision rejection;
|
|
274
|
+
- fresh and existing controller stores;
|
|
275
|
+
- intent creation, eligibility, claims, lease expiry, release, and all resolutions;
|
|
276
|
+
- intent persistence before `ctx.abort()`;
|
|
277
|
+
- agent cancellation and direct user cancellation;
|
|
278
|
+
- terminal timeout and timeout with a natural successor;
|
|
279
|
+
- terminal workflow failure;
|
|
280
|
+
- successful start followed by asynchronous runtime failure before the first prompt;
|
|
281
|
+
- completed and waiting presentation;
|
|
282
|
+
- queued launch failure and immediate start validation failure;
|
|
283
|
+
- controller interruption and claim-loss transfer between two runners;
|
|
284
|
+
- deferred natural prompts and presentations after settlement;
|
|
285
|
+
- busy-session deferral and session-start recovery;
|
|
286
|
+
- duplicate polling and extension restart;
|
|
287
|
+
- send failure and send-before-resolution recovery;
|
|
288
|
+
- Escape, pause, user hold, and shutdown;
|
|
289
|
+
- real-Pi cancellation and timeout ordering with a mock provider.
|
|
290
|
+
|
|
291
|
+
Use temporary databases and run directories. Tests must not call real models, mutate remote systems, or write outside temporary directories.
|
|
292
|
+
|
|
293
|
+
## Verification
|
|
294
|
+
|
|
295
|
+
Run these checks before completion:
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
npm run check
|
|
299
|
+
npm run test:e2e
|
|
300
|
+
npx slophammer-ts@latest dry .
|
|
301
|
+
npx slophammer-ts@latest check . --only ts.dependency-boundaries-required
|
|
302
|
+
git diff --check
|
|
303
|
+
npx -y @simpledoc/simpledoc check
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Keep coverage at or above 85 percent. Inspect the final diff for duplicate turn sources, unresolved claims, stale launch-trigger code, quiet-event regressions, hidden retries, and dependency-boundary violations.
|
|
307
|
+
|
|
308
|
+
## Rollout
|
|
309
|
+
|
|
310
|
+
Keep the controller store at `pi-workflows.controller-store.v1` during alpha. Add the intent table in place and remove the launch-trigger runtime path in the same change.
|
|
311
|
+
|
|
312
|
+
Do not add a schema v2, compatibility reader, dual write, alias, or feature flag. If pending legacy launch notifications exist, stop with a clear reset instruction. Historical terminal run bundles remain readable because this change does not alter the run-bundle contract.
|
|
313
|
+
|
|
314
|
+
Do not publish a package or create a release without separate authorization.
|
|
315
|
+
|
|
316
|
+
## Contract impact
|
|
317
|
+
|
|
318
|
+
- **Session state:** internal workflow messages gain an optional `turnIntentId`; fallback uses `pi-workflows-deferred-turn`.
|
|
319
|
+
- **Controller storage:** add `workflow_turn_intents`; restrict workflow notifications to passive `progress` and `final` kinds.
|
|
320
|
+
- **Run bundles:** unchanged.
|
|
321
|
+
- **Workflow engine:** unchanged.
|
|
322
|
+
- **Workflow author API:** unchanged.
|
|
323
|
+
- **Pi core:** unchanged.
|
|
324
|
+
- **Public Pi APIs:** use existing `sendMessage`, `followUp`, `triggerTurn`, session lifecycle events, and `ctx.abort()`.
|