@osolmaz/pi-workflows 0.13.2 → 0.13.3
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 +5 -3
- package/dist/builtins/autodoc.workflow.d.ts +5 -5
- package/dist/builtins/autoimplement.workflow.d.ts +65 -61
- package/dist/builtins/autoimplement.workflow.js +47 -8
- package/dist/builtins/autoimplement.workflow.js.map +1 -1
- package/dist/builtins/autoplan.workflow.d.ts +5 -5
- package/dist/builtins/catalog.js +1 -1
- package/dist/builtins/change-verification.workflow.d.ts +1 -1
- package/dist/builtins/plain-summary.workflow.d.ts +1 -1
- package/dist/builtins/plan-approval.workflow.d.ts +1 -1
- package/dist/builtins/plan-change.workflow.d.ts +23 -23
- package/dist/builtins/sanity-check.workflow.d.ts +3 -3
- package/dist/builtins/workspace-preparation.workflow.d.ts +1 -1
- package/dist/controllers/index.d.ts +2 -2
- package/dist/controllers/index.js.map +1 -1
- package/dist/controllers/sqlite.js +85 -23
- package/dist/controllers/sqlite.js.map +1 -1
- package/dist/controllers/types.d.ts +34 -0
- package/dist/controllers/workflow-engine-scheduler.d.ts +5 -1
- package/dist/controllers/workflow-engine-scheduler.js +83 -1
- package/dist/controllers/workflow-engine-scheduler.js.map +1 -1
- package/dist/controllers/workflows.d.ts +8 -1
- package/dist/controllers/workflows.js +36 -0
- package/dist/controllers/workflows.js.map +1 -1
- package/dist/extension/controller-host.d.ts +2 -1
- package/dist/extension/controller-host.js +1 -1
- package/dist/extension/controller-host.js.map +1 -1
- package/dist/extension/follow-up-coordinator.d.ts +27 -0
- package/dist/extension/follow-up-coordinator.js +131 -0
- package/dist/extension/follow-up-coordinator.js.map +1 -0
- package/dist/extension/index.d.ts +9 -0
- package/dist/extension/index.js +460 -75
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/session-events.d.ts +2 -3
- package/dist/extension/session-events.js +11 -10
- package/dist/extension/session-events.js.map +1 -1
- package/dist/extension/widget.js +9 -0
- package/dist/extension/widget.js.map +1 -1
- package/dist/state/database.d.ts +2 -1
- package/dist/state/database.js +11 -13
- package/dist/state/database.js.map +1 -1
- package/dist/state/json.js +6 -1
- package/dist/state/json.js.map +1 -1
- package/dist/state/mutation.d.ts +1 -1
- package/dist/state/mutation.js.map +1 -1
- package/dist/state/prune.d.ts +18 -0
- package/dist/state/prune.js +373 -0
- package/dist/state/prune.js.map +1 -0
- package/dist/state/schema.d.ts +1 -1
- package/dist/state/schema.js +116 -18
- package/dist/state/schema.js.map +1 -1
- package/dist/viewer/cli.d.ts +3 -1
- package/dist/viewer/cli.js +55 -4
- package/dist/viewer/cli.js.map +1 -1
- package/dist/viewer/render.js +19 -1
- package/dist/viewer/render.js.map +1 -1
- package/dist/workflows/composition.d.ts +2 -0
- package/dist/workflows/composition.js +15 -0
- package/dist/workflows/composition.js.map +1 -1
- package/dist/workflows/definition.d.ts +6 -3
- package/dist/workflows/definition.js +3 -0
- package/dist/workflows/definition.js.map +1 -1
- package/dist/workflows/engine.d.ts +6 -1
- package/dist/workflows/engine.js +301 -35
- package/dist/workflows/engine.js.map +1 -1
- package/dist/workflows/human-decision.d.ts +1 -0
- package/dist/workflows/human-decision.js +25 -9
- package/dist/workflows/human-decision.js.map +1 -1
- package/dist/workflows/index.d.ts +3 -1
- package/dist/workflows/index.js +2 -0
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/json-patch.d.ts +47 -0
- package/dist/workflows/json-patch.js +298 -0
- package/dist/workflows/json-patch.js.map +1 -0
- package/dist/workflows/schema.js +29 -1
- package/dist/workflows/schema.js.map +1 -1
- package/dist/workflows/settings.d.ts +128 -0
- package/dist/workflows/settings.js +199 -0
- package/dist/workflows/settings.js.map +1 -0
- package/dist/workflows/store.d.ts +75 -2
- package/dist/workflows/store.js +1349 -89
- package/dist/workflows/store.js.map +1 -1
- package/dist/workflows/tool-input.d.ts +22 -0
- package/dist/workflows/tool-input.js +43 -0
- package/dist/workflows/tool-input.js.map +1 -1
- package/dist/workflows/types.d.ts +40 -5
- package/docs/2026-08-25-workflow-follow-ups.md +132 -0
- package/docs/2026-08-25-workflow-settings.md +169 -0
- package/docs/DEFERRED_TURNS.md +6 -0
- package/docs/DESIGN_PHILOSOPHY.md +2 -0
- package/docs/SQLITE_STATE.md +31 -18
- package/docs/WORKFLOW_COMPOSITION.md +15 -0
- package/docs/WORKFLOW_STEP_MESSAGES.md +4 -2
- package/docs/plans/2026-08-25-live-workflow-settings-plan.md +532 -0
- package/docs/plans/2026-08-25-workflow-run-storage-plan.md +67 -0
- package/docs/tui-viewer.md +7 -6
- package/docs/workflows.md +22 -1
- package/examples/workflows/live-settings.workflow.ts +93 -0
- package/herdr-plugin.toml +1 -1
- package/package.json +1 -1
- package/src/builtins/autoimplement.workflow.ts +56 -8
- package/src/builtins/catalog.ts +1 -1
- package/src/controllers/index.ts +6 -0
- package/src/controllers/sqlite.ts +129 -35
- package/src/controllers/types.ts +40 -0
- package/src/controllers/workflow-engine-scheduler.ts +103 -1
- package/src/controllers/workflows.ts +68 -0
- package/src/extension/controller-host.ts +6 -1
- package/src/extension/follow-up-coordinator.ts +151 -0
- package/src/extension/index.ts +538 -88
- package/src/extension/session-events.ts +15 -13
- package/src/extension/widget.ts +8 -0
- package/src/state/database.ts +12 -12
- package/src/state/json.ts +6 -1
- package/src/state/mutation.ts +4 -1
- package/src/state/prune.ts +502 -0
- package/src/state/schema.ts +116 -18
- package/src/viewer/cli.ts +52 -5
- package/src/viewer/render.ts +43 -1
- package/src/workflows/composition.ts +19 -0
- package/src/workflows/definition.ts +19 -5
- package/src/workflows/engine.ts +363 -35
- package/src/workflows/human-decision.ts +41 -11
- package/src/workflows/index.ts +43 -0
- package/src/workflows/json-patch.ts +375 -0
- package/src/workflows/schema.ts +31 -1
- package/src/workflows/settings.ts +430 -0
- package/src/workflows/store.ts +1977 -184
- package/src/workflows/tool-input.ts +58 -0
- package/src/workflows/types.ts +43 -4
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Continue normal work after a workflow finishes
|
|
3
|
+
author: Onur Solmaz <2453968+osolmaz@users.noreply.github.com>
|
|
4
|
+
date: 2026-08-25
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Continue normal work after a workflow finishes
|
|
8
|
+
|
|
9
|
+
A workflow can save several normal user prompts while it runs. Pi Workflows sends them in order only after the workflow is officially terminal and its final response has settled. The completed workflow stays terminal and does not enter its start node again.
|
|
10
|
+
|
|
11
|
+
Follow-up prompts are separate from workflow settings. JSON Patch changes future workflow behavior. Follow-up actions create later normal user turns.
|
|
12
|
+
|
|
13
|
+
## Queue and remove prompts
|
|
14
|
+
|
|
15
|
+
Use the workflow tool once for each prompt:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"action": "queue-follow-up",
|
|
20
|
+
"prompt": "Release the package and verify the published files."
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The result contains a stable `followUpId` and order number.
|
|
25
|
+
|
|
26
|
+
Remove an unsent prompt with:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"action": "remove-follow-up",
|
|
31
|
+
"followUpId": "follow-up-0123456789abcdef0123456789abcdef01234567"
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
A model can remove only a prompt added by the same session tool source. A controller can remove only its own prompt. A verified human can remove any unsent prompt in the owned run.
|
|
36
|
+
|
|
37
|
+
Direct commands are also available:
|
|
38
|
+
|
|
39
|
+
```text
|
|
40
|
+
/workflow queue-follow-up Release the package and verify the published files.
|
|
41
|
+
/workflow remove-follow-up follow-up-0123456789abcdef0123456789abcdef01234567
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Controllers use:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
await ctx.workflows.queueFollowUp({
|
|
48
|
+
requestKey: "release-after-run",
|
|
49
|
+
runId,
|
|
50
|
+
prompt: "Release the package and verify the published files.",
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
await ctx.workflows.removeFollowUp({
|
|
54
|
+
requestKey: "remove-release-after-run",
|
|
55
|
+
runId,
|
|
56
|
+
followUpId,
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The host creates the actor and stable request identity. Prompt data cannot claim human or controller authority.
|
|
61
|
+
|
|
62
|
+
## Ordering and run states
|
|
63
|
+
|
|
64
|
+
Prompts keep database acceptance order. Several user messages can therefore add several prompts while one workflow response is in progress.
|
|
65
|
+
|
|
66
|
+
- Running, paused, parked, and waiting workflows accept new prompts.
|
|
67
|
+
- Pause and park keep prompts queued.
|
|
68
|
+
- A checkpoint continuation takes the queued prompts with it in the same transaction that creates the new run.
|
|
69
|
+
- Successful completion records the terminal run before prompts can become ready.
|
|
70
|
+
- Failed, timed-out, and cancelled workflows cancel every unsent prompt.
|
|
71
|
+
- A terminal workflow rejects new prompts. Repeating an earlier request ID can still return its first result.
|
|
72
|
+
|
|
73
|
+
A repeated request ID with the same prompt returns the first result. Reusing that ID with different content fails.
|
|
74
|
+
|
|
75
|
+
## Final response barrier
|
|
76
|
+
|
|
77
|
+
Each run saves one final-response state:
|
|
78
|
+
|
|
79
|
+
- `not-needed`: the workflow has no final presentation;
|
|
80
|
+
- `pending`: the terminal run has a final response that has not settled;
|
|
81
|
+
- `settled`: the active session branch contains the presentation message and its completed assistant response;
|
|
82
|
+
- `unavailable`: the extension recorded a definite timeout, failure, or restart gap.
|
|
83
|
+
|
|
84
|
+
Successful completion changes queued prompts to `pending_presentation` when a final response is required. It changes them directly to `ready` when no final response is required. A settled or unavailable presentation changes pending prompts to ready.
|
|
85
|
+
|
|
86
|
+
The extension includes the run ID in documented presentation-message details. On restart, it scans the active durable branch for the presentation message and completed assistant response. It saves the observed entry IDs. It does not treat an in-memory event alone as proof.
|
|
87
|
+
|
|
88
|
+
## Delivery
|
|
89
|
+
|
|
90
|
+
The follow-up coordinator handles one prompt at a time:
|
|
91
|
+
|
|
92
|
+
1. Wait for terminal workflow state and a completed final-response barrier.
|
|
93
|
+
2. Wait for the target session to be idle and have no active workflow.
|
|
94
|
+
3. Claim the first ready prompt with a lease.
|
|
95
|
+
4. Scan the active session branch for its stable follow-up ID.
|
|
96
|
+
5. If the message already exists, save its session entry ID without sending it again.
|
|
97
|
+
6. Otherwise, call documented `pi.sendUserMessage` with the prompt and a short nonsecret ID in a Markdown comment.
|
|
98
|
+
7. After Pi saves the normal user message, scan the branch and save its entry ID and send time.
|
|
99
|
+
8. Wait for that user turn and any workflow it starts to finish before taking the next prompt.
|
|
100
|
+
|
|
101
|
+
`pi.sendUserMessage` returns no message ID. Branch evidence is required after a new send and after restart.
|
|
102
|
+
|
|
103
|
+
The follow-up message is a normal user message. It can start normal work or a different workflow. It cannot resume or reactivate the completed workflow.
|
|
104
|
+
|
|
105
|
+
## Failure handling
|
|
106
|
+
|
|
107
|
+
- A crash before claim leaves the prompt ready.
|
|
108
|
+
- A crash after claim lets another process continue after the lease expires.
|
|
109
|
+
- A crash before message append leaves no branch evidence and permits a retry.
|
|
110
|
+
- A crash after append but before the database update is resolved by finding the follow-up ID in the active branch.
|
|
111
|
+
- A definite send failure releases the claim.
|
|
112
|
+
- An unavailable session leaves the prompt pending.
|
|
113
|
+
- Branching follows the active Pi branch. A message on another branch does not count as delivered on the active branch.
|
|
114
|
+
|
|
115
|
+
Current documented Pi APIs cannot guarantee exactly-once model execution. Pi Workflows can guarantee only its saved order, claim, active-branch evidence, and local delivery state. Delivery also needs a later live Pi process, database, target session, and provider. This feature adds no external service.
|
|
116
|
+
|
|
117
|
+
## Saved states
|
|
118
|
+
|
|
119
|
+
A follow-up is in one of these states:
|
|
120
|
+
|
|
121
|
+
- `queued`;
|
|
122
|
+
- `pending_presentation`;
|
|
123
|
+
- `ready`;
|
|
124
|
+
- `sent`;
|
|
125
|
+
- `removed`;
|
|
126
|
+
- `cancelled`.
|
|
127
|
+
|
|
128
|
+
A lease is temporary claim data, not another follow-up state.
|
|
129
|
+
|
|
130
|
+
`workflow_follow_up_queues` stores presentation state and per-run order. `workflow_follow_ups` stores each prompt, source, state, session entry ID, and times. Prompt text stays in the existing content-addressed value store. Normal status views show IDs, order, source type, and state without printing full prompts.
|
|
131
|
+
|
|
132
|
+
This is part of the same alpha hard change as workflow settings. It adds no Pi core change, private Pi API, external queue, compatibility reader, dual write, alias, fallback, or second schema.
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Change workflow settings during a run
|
|
3
|
+
author: Onur Solmaz <2453968+osolmaz@users.noreply.github.com>
|
|
4
|
+
date: 2026-08-25
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Change workflow settings during a run
|
|
8
|
+
|
|
9
|
+
A workflow can expose a JSON settings value that authorized actors can change while the workflow runs. A change affects only future node attempts and future settings routes. It does not change the workflow definition, immutable input, completed work, or a running node.
|
|
10
|
+
|
|
11
|
+
## Declare settings
|
|
12
|
+
|
|
13
|
+
Use `workflowSettings()` in `defineWorkflow()`:
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import {
|
|
17
|
+
allowSettingsPath,
|
|
18
|
+
defineWorkflow,
|
|
19
|
+
settingsRoute,
|
|
20
|
+
workflowSettings,
|
|
21
|
+
} from "@osolmaz/pi-workflows";
|
|
22
|
+
|
|
23
|
+
const settings = workflowSettings({
|
|
24
|
+
initial: {
|
|
25
|
+
instructions: [],
|
|
26
|
+
merge: false,
|
|
27
|
+
route: "normal",
|
|
28
|
+
},
|
|
29
|
+
parse: parseSettings,
|
|
30
|
+
paths: [
|
|
31
|
+
allowSettingsPath("/instructions", {
|
|
32
|
+
read: ["session", "human"],
|
|
33
|
+
add: ["session", "human"],
|
|
34
|
+
remove: ["session", "human"],
|
|
35
|
+
replace: ["session", "human"],
|
|
36
|
+
}),
|
|
37
|
+
allowSettingsPath("/merge", {
|
|
38
|
+
read: ["session", "human"],
|
|
39
|
+
replace: ["human"],
|
|
40
|
+
}),
|
|
41
|
+
allowSettingsPath("/route", {
|
|
42
|
+
read: ["session", "human"],
|
|
43
|
+
replace: ["session", "human"],
|
|
44
|
+
}),
|
|
45
|
+
],
|
|
46
|
+
validateChange: ({ before, after, actor }) => {
|
|
47
|
+
if (actor.type === "session" && before.merge === false && after.merge === true) {
|
|
48
|
+
throw new Error("A model cannot grant merge authority");
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export default defineWorkflow({
|
|
54
|
+
name: "example",
|
|
55
|
+
settings,
|
|
56
|
+
startAt: "choose",
|
|
57
|
+
nodes: {
|
|
58
|
+
choose: settingsRoute({
|
|
59
|
+
run: ({ settings }) => ({ route: settings.route }),
|
|
60
|
+
}),
|
|
61
|
+
normal: compute({ run: ({ settings }) => settings.instructions }),
|
|
62
|
+
careful: compute({ run: ({ settings }) => settings.instructions }),
|
|
63
|
+
},
|
|
64
|
+
edges: [
|
|
65
|
+
{
|
|
66
|
+
from: "choose",
|
|
67
|
+
switch: { on: "$.route", cases: { normal: "normal", careful: "careful" } },
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The parser must validate without changing the patched value. `validateChange` can reject a valid JSON shape because of a cross-field or authority rule.
|
|
74
|
+
|
|
75
|
+
A workflow without `settings` has no editable settings. A change request fails clearly.
|
|
76
|
+
|
|
77
|
+
## Change settings
|
|
78
|
+
|
|
79
|
+
The workflow tool accepts RFC 6902 JSON Patch:
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"action": "change-settings",
|
|
84
|
+
"scopeId": "run-id:$root:1",
|
|
85
|
+
"expectedChangeNumber": 3,
|
|
86
|
+
"patch": [
|
|
87
|
+
{ "op": "replace", "path": "/route", "value": "careful" },
|
|
88
|
+
{ "op": "add", "path": "/instructions/-", "value": "Run the full test suite." }
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
A direct verified user can use:
|
|
94
|
+
|
|
95
|
+
```text
|
|
96
|
+
/workflow change-settings [{"op":"replace","path":"/merge","value":false}]
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Controllers use the same saved change path:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
await ctx.workflows.changeSettings({
|
|
103
|
+
requestKey: "disable-merge",
|
|
104
|
+
runId,
|
|
105
|
+
scopeId,
|
|
106
|
+
expectedChangeNumber: 3,
|
|
107
|
+
patch: [{ op: "replace", path: "/merge", value: false }],
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Each request has a stable host-issued ID. Repeating the same ID and content returns the first result. Reusing an ID with different content fails. When one model response emits several workflow tool calls, the extension applies them in source order so a settings change cannot race past a later step submission.
|
|
112
|
+
|
|
113
|
+
`expectedChangeNumber` protects the complete settings value. A JSON Patch `test` operation can protect one field. When no expected number is supplied, the patch applies to the latest saved value in database acceptance order.
|
|
114
|
+
|
|
115
|
+
## Path permissions
|
|
116
|
+
|
|
117
|
+
Paths use RFC 6901 JSON Pointer. They deny access by default. Rules apply to their path and descendants on segment boundaries. The most specific rule wins.
|
|
118
|
+
|
|
119
|
+
The permissions mean:
|
|
120
|
+
|
|
121
|
+
- `read`: use a path as the source of `test`, `copy`, or `move`;
|
|
122
|
+
- `add`: add a new object value or array item;
|
|
123
|
+
- `remove`: remove a value;
|
|
124
|
+
- `replace`: replace an existing value.
|
|
125
|
+
|
|
126
|
+
`copy` checks source read and destination add or replace. `move` checks source read and remove, then destination add or replace.
|
|
127
|
+
|
|
128
|
+
`read` is not a privacy filter. Workflow code in that scope receives the complete typed settings value.
|
|
129
|
+
|
|
130
|
+
The host records the actor outside the patch:
|
|
131
|
+
|
|
132
|
+
- `session` for a model workflow-tool call;
|
|
133
|
+
- `human` for a direct command or verified human decision;
|
|
134
|
+
- `controller` for a controller request;
|
|
135
|
+
- `policy` only for existing saved policy code.
|
|
136
|
+
|
|
137
|
+
Patch data cannot claim another actor type.
|
|
138
|
+
|
|
139
|
+
## Runtime rules
|
|
140
|
+
|
|
141
|
+
Each node attempt gets one deep-copied, frozen settings value, scope ID, change number, and settings hash. A change accepted while that node runs does not alter it. The next attempt or node captures the latest value.
|
|
142
|
+
|
|
143
|
+
Use `settingsRoute()` when a future route must use the latest settings. It is a pure compute node. The database compares its change number in the same transaction that saves the route result. If the number is stale, the engine reruns only that pure route. It never repeats an agent step, shell command, function action, or other external effect because settings changed.
|
|
144
|
+
|
|
145
|
+
A settings scope accepts changes when its workflow chain is running, paused, parked, or waiting at a checkpoint. A terminal completed, failed, timed-out, or cancelled run rejects new changes.
|
|
146
|
+
|
|
147
|
+
## Included workflows and checkpoints
|
|
148
|
+
|
|
149
|
+
The root workflow has one settings scope when it declares settings. Each included-workflow invocation that declares settings gets its own scope. Its ID uses the root run, mount path, and saved invocation number. Re-entry gets fresh settings.
|
|
150
|
+
|
|
151
|
+
Parent and child settings do not mix automatically. The child initializer receives its mapped child input.
|
|
152
|
+
|
|
153
|
+
A checkpoint creates a continuation run. The transaction that creates that run transfers the active settings scopes to it. Scope IDs, change numbers, saved changes, and current values remain unchanged. This keeps one ordered settings history across the logical workflow chain.
|
|
154
|
+
|
|
155
|
+
## Limits
|
|
156
|
+
|
|
157
|
+
One patch can contain at most 256 operations and 64 KiB. One JSON Pointer can contain at most 1 KiB. The resulting settings value can contain at most 256 KiB.
|
|
158
|
+
|
|
159
|
+
The engine rejects malformed operations, unknown fields, invalid indexes, invalid pointer escapes, non-JSON values, unsafe object access, and invalid moves. A rejected patch changes nothing.
|
|
160
|
+
|
|
161
|
+
Workflow settings must not contain credentials or raw secrets. Use the existing credential stores and authority checks instead.
|
|
162
|
+
|
|
163
|
+
## Saved state and inspection
|
|
164
|
+
|
|
165
|
+
Each settings scope uses one `resources` row of type `settings`. Its resource revision is the public change number. `workflow_settings` stores the current value. `workflow_setting_changes` stores every accepted patch, actor, source, old hash, new hash, and time.
|
|
166
|
+
|
|
167
|
+
Node attempts store the scope ID, change number, and settings hash they used. Workflow status, the TypeScript viewer, and `piw` show bounded settings data without printing private values by default.
|
|
168
|
+
|
|
169
|
+
This is an alpha hard change. The schema keeps its existing v1 name and changes in place. Incompatible old local state remains untouched and must be backed up or reset. There is no old-state reader, second schema, dual write, alias, fallback, or feature flag.
|
package/docs/DEFERRED_TURNS.md
CHANGED
|
@@ -260,6 +260,12 @@ The `launch_failure` notification kind is removed. Queued launch failure creates
|
|
|
260
260
|
|
|
261
261
|
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.
|
|
262
262
|
|
|
263
|
+
## Post-completion follow-ups
|
|
264
|
+
|
|
265
|
+
Deferred turns repair one stranded workflow turn after interruption. Ordered post-completion prompts are different. They represent user-requested normal work after successful completion, use `workflow_follow_up_queues` and `workflow_follow_ups`, and are delivered by the separate follow-up coordinator. Neither feature reads or changes the other's rows.
|
|
266
|
+
|
|
267
|
+
See [Continue normal work after a workflow finishes](2026-08-25-workflow-follow-ups.md).
|
|
268
|
+
|
|
263
269
|
## Availability limits
|
|
264
270
|
|
|
265
271
|
Pi Workflows can guarantee only the facts under its control:
|
|
@@ -32,6 +32,8 @@ Runs should survive interruption and remain safe to resume. Save the run input a
|
|
|
32
32
|
|
|
33
33
|
Immutable SQLite events are the record of what happened. Domain rows are current projections written in the same transaction. Viewers derive their answers from those facts instead of creating another source of truth.
|
|
34
34
|
|
|
35
|
+
Live workflow settings follow the same rule. The workflow owns one typed JSON value and path policy. The engine owns ordered JSON Patch application and fixed node bindings. Lifecycle actions, such as queued post-completion prompts, stay outside the settings value.
|
|
36
|
+
|
|
35
37
|
Reading shared state never gives mutation authority. Every durable write checks its actor, expected resource revision, and current lease generation when ownership is required. Follow-up work uses deterministic effects and idempotent receipts so partial failure can converge safely.
|
|
36
38
|
|
|
37
39
|
## Boundaries
|
package/docs/SQLITE_STATE.md
CHANGED
|
@@ -18,6 +18,7 @@ The database stores:
|
|
|
18
18
|
- human-decision requests, submissions, resolutions, and cancellations
|
|
19
19
|
- controller resources, finalizers, effects, and child workflows
|
|
20
20
|
- notifications and deferred turns
|
|
21
|
+
- workflow settings, accepted JSON Patch changes, and post-completion follow-up prompts
|
|
21
22
|
- nonsecret channel cursors, inbox records, messages, and settlement receipts
|
|
22
23
|
- canonical JSON, text, and large text values
|
|
23
24
|
|
|
@@ -57,7 +58,7 @@ Four record groups provide the common lifecycle rules.
|
|
|
57
58
|
|
|
58
59
|
### Resources
|
|
59
60
|
|
|
60
|
-
`resources` identifies each mutable aggregate and holds its current revision. Runs, decisions, controller resources, effects, channels, notifications, deferred turns, and session segments have stable resource identities.
|
|
61
|
+
`resources` identifies each mutable aggregate and holds its current revision. Runs, settings scopes, follow-up queues and items, decisions, controller resources, effects, channels, notifications, deferred turns, and session segments have stable resource identities.
|
|
61
62
|
|
|
62
63
|
Every accepted domain command compares its expected revision and increments it once.
|
|
63
64
|
|
|
@@ -93,18 +94,20 @@ Local effects use deterministic transactions. Run queue settlement effects are c
|
|
|
93
94
|
|
|
94
95
|
The shared records do not replace domain schemas. The following `STRICT` tables keep the state explicit:
|
|
95
96
|
|
|
96
|
-
| Area | Tables
|
|
97
|
-
| ------------------- |
|
|
98
|
-
| Schema and projects | `schema_meta`, `projects`
|
|
99
|
-
| Content | `blobs`
|
|
100
|
-
| Shared lifecycle | `resources`, `leases`, `events`
|
|
101
|
-
| Workflows | `workflow_definitions`, `runs`, `run_steps`, `run_bindings`, `run_queue`, `node_attempts`, `workflow_updates` |
|
|
102
|
-
|
|
|
103
|
-
|
|
|
104
|
-
|
|
|
105
|
-
|
|
|
106
|
-
|
|
|
107
|
-
|
|
|
97
|
+
| Area | Tables |
|
|
98
|
+
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
99
|
+
| Schema and projects | `schema_meta`, `projects` |
|
|
100
|
+
| Content | `blobs` |
|
|
101
|
+
| Shared lifecycle | `resources`, `leases`, `events` |
|
|
102
|
+
| Workflows | `workflow_definitions`, `runs`, `run_sources`, `run_steps`, `run_bindings`, `run_queue`, `node_attempts`, `workflow_updates` |
|
|
103
|
+
| Live settings | `workflow_settings`, `workflow_setting_changes` |
|
|
104
|
+
| Post-run follow-ups | `workflow_follow_up_queues`, `workflow_follow_ups` |
|
|
105
|
+
| Session capture | `session_segments`, `session_entries`, `attempt_entries`, `session_events` |
|
|
106
|
+
| Human decisions | `human_decisions`, `human_decision_resolutions`, `human_decision_submissions`, `continuations` |
|
|
107
|
+
| Controllers | `controller_resources`, `controller_finalizers`, `controller_queue`, `controller_workflows` |
|
|
108
|
+
| Effects | `effects`, `effect_attempts` |
|
|
109
|
+
| Pi delivery | `notifications`, `turn_intents` |
|
|
110
|
+
| Channels | `channels`, `channel_cursors`, `channel_inbox`, `channel_messages`, `channel_message_parts` |
|
|
108
111
|
|
|
109
112
|
Foreign keys join projects, runs, attempts, decisions, controllers, effects, and channel records. Partial unique indexes enforce one active node attempt per run, one queued or running reservation per Pi session, one decision winner, and one deterministic effect key. A parked waiting parent does not block its continuation. Reserving that continuation settles the parked parent queue in the same transaction, so a failed reservation leaves the parent recoverable.
|
|
110
113
|
|
|
@@ -112,15 +115,17 @@ Foreign keys join projects, runs, attempts, decisions, controllers, effects, and
|
|
|
112
115
|
|
|
113
116
|
`blobs` stores canonical JSON and UTF-8 text as bytes. Its primary key is the 32-byte SHA-256 digest of the bytes.
|
|
114
117
|
|
|
115
|
-
Insertion verifies the digest, media type, byte length, and exact bytes. Repeated content adopts the existing row. This replaces separate artifact files while keeping
|
|
118
|
+
Insertion verifies the digest, media type, byte length, and exact bytes. Repeated content adopts the existing row. This replaces separate artifact files while keeping outputs, errors, settled Pi entries, and rendered channel text deduplicated. Opening the database never deletes blobs. The explicit prune command removes unreferenced blobs after it deletes safe old run trees.
|
|
116
119
|
|
|
117
|
-
Runs do not store a nested `WorkflowRunState` blob. `runs` stores
|
|
120
|
+
Runs do not store a nested `WorkflowRunState` blob. `runs` stores run-level facts and hashes for independent values. `run_sources` stores source identity without source JSON blobs. `node_attempts` stores structured workflow outputs and small execution receipts. `session_entries` is the only stored copy of each settled Pi entry. `attempt_entries` links an attempt to its prompt, response, first, and last Pi entries. `run_steps` stores ordered attempt membership and only stores an output override when a continuation changes a carried checkpoint answer.
|
|
121
|
+
|
|
122
|
+
Readers derive `steps`, `outputs`, `results`, carried-step count, current-node fields, waiting state, source objects, and continuation decision receipts from these rows. Compact trace events do not copy prompts, node outputs, run inputs, final outputs, action receipts, or assistant receipts.
|
|
118
123
|
|
|
119
124
|
### Assistant-message attempts
|
|
120
125
|
|
|
121
126
|
An agent definition records `expectedOutput` as either a submitted-output description or `{ "kind": "assistant-message", "maxChars"?: number }`. Omitted `maxChars` means that Pi Workflows adds no character limit.
|
|
122
127
|
|
|
123
|
-
A completed assistant-message attempt
|
|
128
|
+
A completed interactive assistant-message attempt uses the settled Pi response entry as its output. It does not store a second output blob. Its small receipt keeps the text digest, final Pi session entry ID, optional author-supplied limit, and whether recovery adopted an existing response. A noninteractive attempt with no captured response entry keeps one normal output blob.
|
|
124
129
|
|
|
125
130
|
An interrupted assistant-message attempt keeps its attempt ID when the origin Pi session resumes it. The executor adopts a matching completed assistant child from the active Pi branch instead of displaying the response twice. Submitted and non-agent attempts keep their normal fresh-attempt resume behavior.
|
|
126
131
|
|
|
@@ -145,7 +150,7 @@ COMMIT
|
|
|
145
150
|
|
|
146
151
|
Any failed check rolls back the complete command.
|
|
147
152
|
|
|
148
|
-
Session-event batches use `session_events` as their journal. They update the contiguous segment counter in the same transaction and do not create a generic `session.events_appended` event for each flush. The recorder stores
|
|
153
|
+
Session-event batches use `session_events` as their journal. They update the contiguous segment counter in the same transaction and do not create a generic `session.events_appended` event for each flush. The recorder stores order, IDs, roles, status, and timing boundaries. It discards token deltas, completed text, thinking text, tool arguments, tool results, and incremental tool progress. The settled Pi entries keep the replay content.
|
|
149
154
|
|
|
150
155
|
A TypeScript write permit carries the expected facts between layers. It is not authority by itself. The store verifies durable ownership and revision data again inside the transaction.
|
|
151
156
|
|
|
@@ -176,7 +181,11 @@ The same rule applies to run terminal outcomes, continuation admission, queue se
|
|
|
176
181
|
|
|
177
182
|
## Read contract
|
|
178
183
|
|
|
179
|
-
Status is a pure projection of domain rows, immutable facts, current leases, and effect
|
|
184
|
+
Status is a pure projection of domain rows, immutable facts, current leases, and effect results.
|
|
185
|
+
|
|
186
|
+
A settings scope uses its resource revision as its public change number. Each accepted patch, current value, and node binding is saved in one transaction. A checkpoint continuation keeps the same settings resources and transfers them to the continuation run.
|
|
187
|
+
|
|
188
|
+
A follow-up queue records acceptance order and final-presentation state. Successful terminalization changes queued items to ready or pending presentation in the same transaction as the terminal run fact. Failure, timeout, and cancellation cancel unsent items. Delivery uses item leases and active Pi branch evidence.
|
|
180
189
|
|
|
181
190
|
- A terminal run fact overrides stale queue presentation.
|
|
182
191
|
- An accepted decision is accepted even if its continuation effect is still pending.
|
|
@@ -213,11 +222,15 @@ Supported commands are:
|
|
|
213
222
|
pi-workflows state status
|
|
214
223
|
pi-workflows state verify
|
|
215
224
|
pi-workflows state backup /absolute/path/to/state-backup.sqlite
|
|
225
|
+
pi-workflows state prune --before 2026-08-01T00:00:00Z --dry-run
|
|
226
|
+
pi-workflows state prune --before 2026-08-01T00:00:00Z --backup /absolute/path/to/before-prune.sqlite --apply
|
|
216
227
|
```
|
|
217
228
|
|
|
218
229
|
`status` reports only safe counts, file size, active leases, and unsettled effects.
|
|
219
230
|
It does not print actor IDs, channel references, payloads, or credentials.
|
|
220
231
|
|
|
232
|
+
`prune --dry-run` reports complete terminal run trees older than the cutoff and the trees that safety checks block. It does not change the database. `prune --apply` requires a new absolute backup path. It verifies the backup, locks maintenance, rechecks the same selection in an exclusive transaction, and refuses trees with live queues, active leases, unsettled effects, controller references, channel references, or step links from runs outside the tree. It deletes the safe aggregates, removes blobs with no remaining foreign-key reference, checkpoints the WAL, vacuums the file, and runs integrity and foreign-key checks. Pi Workflows never runs prune at startup.
|
|
233
|
+
|
|
221
234
|
## Alpha cutover
|
|
222
235
|
|
|
223
236
|
This is a hard cut. Pi Workflows has no normal reader or writer for older live storage. It does not use dual reads, dual writes, aliases, versioned state roots, or automatic import.
|
|
@@ -198,6 +198,21 @@ A child cannot read parent state except through mapped input. A parent sees only
|
|
|
198
198
|
|
|
199
199
|
Each mount entry starts with empty local outputs and results. Earlier invocation data stays in the trace but cannot satisfy callbacks in a later invocation. The latest named exit replaces the parent-visible mount output.
|
|
200
200
|
|
|
201
|
+
### Workflow settings
|
|
202
|
+
|
|
203
|
+
A root workflow and each included-workflow invocation can declare separate settings. Included settings are isolated by default and use the mount path plus saved invocation number as their scope. Re-entry creates fresh settings. A child initializer receives the mapped child input.
|
|
204
|
+
|
|
205
|
+
A parent can override one invocation's initial child settings explicitly:
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
includeWorkflow(child, {
|
|
209
|
+
input: ({ outputs }) => ({ task: outputs.plan }),
|
|
210
|
+
settings: ({ settings }) => ({ ...settings, route: "careful" }),
|
|
211
|
+
});
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
A checkpoint continuation keeps the same active settings scopes, change numbers, and saved changes. The continuation transaction transfers those scopes and queued follow-ups to the new run before execution resumes.
|
|
215
|
+
|
|
201
216
|
### Limits and progress
|
|
202
217
|
|
|
203
218
|
The root `maxSteps` limits all real node attempts in the run. Each child `maxSteps` limits one invocation. Include entry and exit transitions are recorded but do not count as user-authored node attempts.
|
|
@@ -55,6 +55,8 @@ The request carries optional presentation data for the run title and node status
|
|
|
55
55
|
|
|
56
56
|
One pure formatter remains responsible for the model prompt used by both executors. Interactive delivery must not shorten, summarize, or rebuild the model prompt from display fields.
|
|
57
57
|
|
|
58
|
+
Before the step contract, the formatter adds the active settings scope, change number, bounded current value, allowed model paths, and exact `change-settings` action when that scope declares settings. It also shows the `queue-follow-up` and `remove-follow-up` actions. Actor identity is never part of model input. The extension derives it from the documented tool call.
|
|
59
|
+
|
|
58
60
|
## Compact display
|
|
59
61
|
|
|
60
62
|
The collapsed card shows only useful workflow identity and current work. For example:
|
|
@@ -104,7 +106,7 @@ The two message types must not share delivery code that can accidentally change
|
|
|
104
106
|
|
|
105
107
|
Interactive step deliveries use `sendMessage` instead of `sendUserMessage`. Existing session entries remain readable and are not rewritten.
|
|
106
108
|
|
|
107
|
-
The custom prompt and visible assistant response are normal documented Pi session messages. pi-workflows adds no Pi session schema, private entry type, or separate persistent store. SQLite stores the
|
|
109
|
+
The custom prompt and visible assistant response are normal documented Pi session messages. pi-workflows adds no Pi session schema, private entry type, or separate persistent store. SQLite stores each settled Pi entry once. Small relational links connect the workflow attempt to its prompt, response, first, and last entries. The attempt keeps only the assistant digest receipt. It does not copy the prompt or visible response into another blob.
|
|
108
110
|
|
|
109
111
|
If the renderer is unavailable, Pi still retains the custom message content. pi-workflows does not add a fallback path that sends a duplicate user message.
|
|
110
112
|
|
|
@@ -123,7 +125,7 @@ Tests verify:
|
|
|
123
125
|
- interactive and RPC executors give the model the same complete prompt
|
|
124
126
|
- one step message starts one model turn
|
|
125
127
|
- collapsed rendering does not show the full prompt
|
|
126
|
-
- expanded rendering shows the full prompt and exact contract ids
|
|
128
|
+
- expanded rendering shows the full prompt, settings scope and change number, allowed paths, and exact contract ids
|
|
127
129
|
- long and missing display fields render safely
|
|
128
130
|
- reminders and resumed deliveries keep the active attempt id
|
|
129
131
|
- submitted steps still reject stale attempts after timeout or cancellation
|