@osolmaz/pi-workflows 0.16.2 → 0.16.4
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 +41 -41
- package/dist/builtins/monitor.workflow.d.ts +1 -1
- package/dist/builtins/monitor.workflow.js +8 -8
- package/dist/builtins/pi-agent-group.js +3 -3
- package/dist/builtins/pi-agent-group.js.map +1 -1
- package/dist/channels/adapter-entry.js +14 -14
- package/dist/channels/adapter-entry.js.map +1 -1
- package/dist/client/client.d.ts +6 -6
- package/dist/client/client.js +27 -27
- package/dist/client/client.js.map +1 -1
- package/dist/client/protocol.d.ts +4 -1
- package/dist/client/protocol.js +17 -11
- package/dist/client/protocol.js.map +1 -1
- package/dist/client/resolver.d.ts +4 -4
- package/dist/client/view.d.ts +1 -1
- package/dist/extension/index.d.ts +1 -1
- package/dist/extension/index.js +32 -32
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/recorder.d.ts +1 -1
- package/dist/extension/recorder.js +1 -1
- package/dist/extension/recorder.js.map +1 -1
- package/dist/extension/remote-recorder-store.d.ts +1 -1
- package/dist/extension/remote-recorder-store.js +2 -2
- package/dist/extension/remote-recorder-store.js.map +1 -1
- package/dist/extension/{controller-command.d.ts → resource-manager-command.d.ts} +6 -6
- package/dist/extension/{controller-command.js → resource-manager-command.js} +7 -7
- package/dist/extension/resource-manager-command.js.map +1 -0
- package/dist/extension/session-delivery.d.ts +1 -1
- package/dist/extension/session-delivery.js +2 -2
- package/dist/extension/session-delivery.js.map +1 -1
- package/dist/extension/session-view.d.ts +1 -1
- package/dist/extension/session-view.js +1 -1
- package/dist/extension/session-view.js.map +1 -1
- package/dist/extension/workflow-message-coordinator.d.ts +1 -1
- package/dist/extension/workflow-message-coordinator.js +6 -6
- package/dist/extension/workflow-message-coordinator.js.map +1 -1
- package/dist/resource-managers/conditions.d.ts +6 -0
- package/dist/{controllers → resource-managers}/conditions.js +3 -3
- package/dist/resource-managers/conditions.js.map +1 -0
- package/dist/resource-managers/definition.d.ts +6 -0
- package/dist/resource-managers/definition.js +45 -0
- package/dist/resource-managers/definition.js.map +1 -0
- package/dist/resource-managers/effects.d.ts +15 -0
- package/dist/{controllers → resource-managers}/effects.js +2 -2
- package/dist/resource-managers/effects.js.map +1 -0
- package/dist/resource-managers/errors.d.ts +12 -0
- package/dist/resource-managers/errors.js +25 -0
- package/dist/resource-managers/errors.js.map +1 -0
- package/dist/resource-managers/index.d.ts +12 -0
- package/dist/resource-managers/index.js +12 -0
- package/dist/resource-managers/index.js.map +1 -0
- package/dist/resource-managers/json.js.map +1 -0
- package/dist/resource-managers/loader.d.ts +23 -0
- package/dist/resource-managers/loader.js +75 -0
- package/dist/resource-managers/loader.js.map +1 -0
- package/dist/resource-managers/results.d.ts +5 -0
- package/dist/resource-managers/results.js.map +1 -0
- package/dist/resource-managers/runtime.d.ts +59 -0
- package/dist/{controllers/manager.js → resource-managers/runtime.js} +57 -57
- package/dist/resource-managers/runtime.js.map +1 -0
- package/dist/{controllers → resource-managers}/sqlite.d.ts +44 -44
- package/dist/{controllers → resource-managers}/sqlite.js +139 -130
- package/dist/resource-managers/sqlite.js.map +1 -0
- package/dist/{controllers → resource-managers}/store.d.ts +33 -33
- package/dist/{controllers → resource-managers}/store.js.map +1 -1
- package/dist/{controllers → resource-managers}/types.d.ts +41 -41
- package/dist/{controllers → resource-managers}/types.js.map +1 -1
- package/dist/resource-managers/workflows.d.ts +34 -0
- package/dist/{controllers → resource-managers}/workflows.js +15 -15
- package/dist/resource-managers/workflows.js.map +1 -0
- package/dist/server/channel-effects.js.map +1 -0
- package/dist/{host → server}/channel-supervisor.d.ts +6 -6
- package/dist/{host → server}/channel-supervisor.js +3 -3
- package/dist/server/channel-supervisor.js.map +1 -0
- package/dist/{host/child-worker-supervisor.d.ts → server/child-runner-supervisor.d.ts} +15 -13
- package/dist/{host/child-worker-supervisor.js → server/child-runner-supervisor.js} +6 -5
- package/dist/server/child-runner-supervisor.js.map +1 -0
- package/dist/{host → server}/processes.d.ts +3 -3
- package/dist/{host → server}/processes.js +3 -3
- package/dist/server/processes.js.map +1 -0
- package/dist/{host → server}/resolver-entry.d.ts +6 -6
- package/dist/{host → server}/resolver-entry.js +14 -14
- package/dist/server/resolver-entry.js.map +1 -0
- package/dist/server/resource-runner-entry.d.ts +2 -0
- package/dist/{host/controller-worker-entry.js → server/resource-runner-entry.js} +33 -33
- package/dist/server/resource-runner-entry.js.map +1 -0
- package/dist/server/resource-runner-protocol.d.ts +36 -0
- package/dist/server/resource-runner-protocol.js +49 -0
- package/dist/server/resource-runner-protocol.js.map +1 -0
- package/dist/server/resource-runner-supervisor.d.ts +21 -0
- package/dist/{host/worker-supervisor.js → server/resource-runner-supervisor.js} +17 -17
- package/dist/server/resource-runner-supervisor.js.map +1 -0
- package/dist/{host → server}/rpc-bridge.d.ts +3 -3
- package/dist/{host → server}/rpc-bridge.js +3 -3
- package/dist/server/rpc-bridge.js.map +1 -0
- package/dist/{host → server}/rpc-executor.d.ts +1 -1
- package/dist/{host → server}/rpc-executor.js +3 -3
- package/dist/server/rpc-executor.js.map +1 -0
- package/dist/{host/host-entry.js → server/server-entry.js} +6 -6
- package/dist/server/server-entry.js.map +1 -0
- package/dist/{host/runner.d.ts → server/server.d.ts} +39 -38
- package/dist/{host/runner.js → server/server.js} +408 -374
- package/dist/server/server.js.map +1 -0
- package/dist/{host → server}/state.d.ts +25 -25
- package/dist/{host → server}/state.js +49 -49
- package/dist/server/state.js.map +1 -0
- package/dist/{host → server}/view.d.ts +7 -7
- package/dist/{host → server}/view.js +13 -13
- package/dist/server/view.js.map +1 -0
- package/dist/server/workflow-runner-content.d.ts +6 -0
- package/dist/server/workflow-runner-content.js +96 -0
- package/dist/server/workflow-runner-content.js.map +1 -0
- package/dist/{host/worker-entry.d.ts → server/workflow-runner-entry.d.ts} +3 -3
- package/dist/{host/worker-entry.js → server/workflow-runner-entry.js} +68 -35
- package/dist/server/workflow-runner-entry.js.map +1 -0
- package/dist/server/workflow-runner-protocol.d.ts +69 -0
- package/dist/server/workflow-runner-protocol.js +176 -0
- package/dist/server/workflow-runner-protocol.js.map +1 -0
- package/dist/{host/worker-store.d.ts → server/workflow-runner-store.d.ts} +11 -11
- package/dist/{host/worker-store.js → server/workflow-runner-store.js} +8 -8
- package/dist/server/workflow-runner-store.js.map +1 -0
- package/dist/server/workflow-runner-supervisor.d.ts +22 -0
- package/dist/server/workflow-runner-supervisor.js +56 -0
- package/dist/server/workflow-runner-supervisor.js.map +1 -0
- package/dist/state/prune.d.ts +1 -1
- package/dist/state/prune.js +17 -10
- package/dist/state/prune.js.map +1 -1
- package/dist/state/schema.js +2 -2
- package/dist/state/workflow-messages.d.ts +1 -1
- package/dist/state/workflow-messages.js +1 -1
- package/dist/state/workflow-messages.js.map +1 -1
- package/dist/viewer/backup.js +1 -1
- package/dist/viewer/backup.js.map +1 -1
- package/dist/viewer/cli.d.ts +2 -2
- package/dist/viewer/cli.js +46 -32
- package/dist/viewer/cli.js.map +1 -1
- package/dist/viewer/tui.d.ts +1 -1
- package/dist/viewer/tui.js +1 -1
- package/dist/viewer/tui.js.map +1 -1
- package/dist/workflows/command-batch.js +2 -2
- package/dist/workflows/engine.js +14 -15
- package/dist/workflows/engine.js.map +1 -1
- package/dist/workflows/store.d.ts +12 -11
- package/dist/workflows/store.js +24 -18
- package/dist/workflows/store.js.map +1 -1
- package/dist/workflows/types.d.ts +2 -2
- package/docs/2026-08-20-durable-workflow-launch-plan.md +12 -12
- package/docs/2026-08-25-workflow-follow-ups.md +7 -7
- package/docs/2026-08-25-workflow-settings.md +3 -3
- 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 +5 -5
- package/docs/2026-09-01-unified-workflow-client-plan.md +6 -6
- package/docs/2026-09-02-installed-live-e2e-plan.md +3 -3
- package/docs/2026-09-02-unify-workflow-messages-plan.md +8 -8
- package/docs/2026-09-04-workflow-run-state-plan.md +6 -6
- package/docs/DEFERRED_TURNS.md +7 -7
- package/docs/DESIGN_PHILOSOPHY.md +1 -1
- package/docs/HUMAN_DECISIONS.md +11 -11
- package/docs/HUMAN_DECISION_PRESENTATIONS.md +4 -4
- package/docs/MONITOR.md +1 -1
- package/docs/RESOURCE_MANAGERS.md +221 -0
- package/docs/SQLITE_STATE.md +35 -33
- package/docs/WORKFLOW_COMPOSITION.md +1 -1
- package/docs/{WORKFLOW_HOST.md → WORKFLOW_SERVER.md} +125 -119
- package/docs/WORKFLOW_STEP_MESSAGES.md +18 -18
- package/docs/WORKFLOW_UPDATES.md +16 -16
- package/docs/development.md +15 -14
- package/docs/live-replay-protocol.md +20 -20
- package/docs/plans/2026-08-04-controller-runtime-plan.md +25 -25
- package/docs/plans/2026-08-05-always-on-workflows-plan.md +8 -8
- package/docs/plans/2026-08-10-agent-managed-monitor-workflows-plan.md +2 -2
- package/docs/plans/2026-08-13-session-addressed-workflow-notifications-plan.md +1 -1
- package/docs/plans/2026-08-16-workflow-updates-plan.md +5 -5
- package/docs/plans/2026-08-19-human-decision-gates-plan.md +1 -1
- package/docs/plans/2026-08-20-bounded-command-batches-plan.md +1 -1
- package/docs/plans/2026-08-21-deferred-turn-intents-plan.md +3 -3
- package/docs/plans/2026-08-21-sanity-check-plan.md +2 -2
- package/docs/plans/2026-08-23-assistant-agent-completion-plan.md +5 -5
- package/docs/plans/2026-08-23-sqlite-state-plan.md +25 -25
- package/docs/plans/2026-08-25-live-workflow-settings-plan.md +4 -4
- package/docs/plans/2026-08-27-workflow-terminal-restart-plan.md +3 -3
- package/docs/plans/2026-09-04-workflow-runner-resume-state-plan.md +318 -0
- package/docs/plans/piw-viewer-experience-implementation-plan.md +1 -1
- package/docs/tui-viewer.md +9 -9
- package/docs/workflows.md +72 -66
- package/examples/{controllers/pull-request.controller.ts → resource-managers/pull-request.resource-manager.ts} +12 -12
- package/herdr-plugin.toml +1 -1
- package/package.json +12 -11
- package/protocol/client.v1.schema.json +7 -7
- package/protocol/fixtures/client-v1.json +1 -1
- package/src/builtins/monitor.workflow.ts +9 -9
- package/src/builtins/pi-agent-group.ts +3 -3
- package/src/channels/adapter-entry.ts +14 -14
- package/src/client/client.ts +33 -32
- package/src/client/protocol.ts +16 -11
- package/src/client/resolver.ts +4 -4
- package/src/client/view.ts +1 -1
- package/src/extension/index.ts +44 -34
- package/src/extension/recorder.ts +1 -1
- package/src/extension/remote-recorder-store.ts +2 -2
- package/src/extension/resource-manager-command.ts +45 -0
- package/src/extension/session-delivery.ts +2 -2
- package/src/extension/session-view.ts +1 -1
- package/src/extension/workflow-message-coordinator.ts +6 -6
- package/src/{controllers → resource-managers}/conditions.ts +23 -23
- package/src/resource-managers/definition.ts +71 -0
- package/src/{controllers → resource-managers}/effects.ts +9 -9
- package/src/resource-managers/errors.ts +27 -0
- package/src/resource-managers/index.ts +88 -0
- package/src/resource-managers/loader.ts +111 -0
- package/src/{controllers → resource-managers}/results.ts +4 -4
- package/src/{controllers/manager.ts → resource-managers/runtime.ts} +95 -92
- package/src/{controllers → resource-managers}/sqlite.ts +198 -186
- package/src/{controllers → resource-managers}/store.ts +46 -38
- package/src/{controllers → resource-managers}/types.ts +50 -41
- package/src/{controllers → resource-managers}/workflows.ts +43 -41
- package/src/{host → server}/channel-supervisor.ts +8 -8
- package/src/{host/child-worker-supervisor.ts → server/child-runner-supervisor.ts} +22 -18
- package/src/{host → server}/processes.ts +3 -3
- package/src/{host → server}/resolver-entry.ts +28 -25
- package/src/{host/controller-worker-entry.ts → server/resource-runner-entry.ts} +67 -65
- package/src/server/resource-runner-protocol.ts +103 -0
- package/src/server/resource-runner-supervisor.ts +76 -0
- package/src/{host → server}/rpc-bridge.ts +3 -3
- package/src/{host → server}/rpc-executor.ts +4 -4
- package/src/{host/host-entry.ts → server/server-entry.ts} +5 -5
- package/src/{host/runner.ts → server/server.ts} +526 -461
- package/src/{host → server}/state.ts +78 -67
- package/src/{host → server}/view.ts +15 -15
- package/src/server/workflow-runner-content.ts +119 -0
- package/src/{host/worker-entry.ts → server/workflow-runner-entry.ts} +98 -57
- package/src/server/workflow-runner-protocol.ts +241 -0
- package/src/{host/worker-store.ts → server/workflow-runner-store.ts} +18 -20
- package/src/server/workflow-runner-supervisor.ts +79 -0
- package/src/state/prune.ts +27 -9
- package/src/state/schema.ts +2 -2
- package/src/state/workflow-messages.ts +1 -1
- package/src/viewer/backup.ts +1 -1
- package/src/viewer/cli.ts +50 -36
- package/src/viewer/tui.ts +1 -1
- package/src/workflows/command-batch.ts +2 -2
- package/src/workflows/engine.ts +14 -15
- package/src/workflows/store.ts +31 -28
- package/src/workflows/types.ts +2 -2
- package/dist/controllers/conditions.d.ts +0 -6
- package/dist/controllers/conditions.js.map +0 -1
- package/dist/controllers/definition.d.ts +0 -6
- package/dist/controllers/definition.js +0 -45
- package/dist/controllers/definition.js.map +0 -1
- package/dist/controllers/effects.d.ts +0 -15
- package/dist/controllers/effects.js.map +0 -1
- package/dist/controllers/errors.d.ts +0 -12
- package/dist/controllers/errors.js +0 -25
- package/dist/controllers/errors.js.map +0 -1
- package/dist/controllers/index.d.ts +0 -12
- package/dist/controllers/index.js +0 -12
- package/dist/controllers/index.js.map +0 -1
- package/dist/controllers/json.js.map +0 -1
- package/dist/controllers/loader.d.ts +0 -23
- package/dist/controllers/loader.js +0 -74
- package/dist/controllers/loader.js.map +0 -1
- package/dist/controllers/manager.d.ts +0 -59
- package/dist/controllers/manager.js.map +0 -1
- package/dist/controllers/results.d.ts +0 -5
- package/dist/controllers/results.js.map +0 -1
- package/dist/controllers/sqlite.js.map +0 -1
- package/dist/controllers/workflows.d.ts +0 -34
- package/dist/controllers/workflows.js.map +0 -1
- package/dist/extension/controller-command.js.map +0 -1
- package/dist/host/channel-effects.js.map +0 -1
- package/dist/host/channel-supervisor.js.map +0 -1
- package/dist/host/child-worker-supervisor.js.map +0 -1
- package/dist/host/controller-worker-entry.d.ts +0 -2
- package/dist/host/controller-worker-entry.js.map +0 -1
- package/dist/host/controller-worker-protocol.d.ts +0 -36
- package/dist/host/controller-worker-protocol.js +0 -49
- package/dist/host/controller-worker-protocol.js.map +0 -1
- package/dist/host/controller-worker-supervisor.d.ts +0 -21
- package/dist/host/controller-worker-supervisor.js +0 -54
- package/dist/host/controller-worker-supervisor.js.map +0 -1
- package/dist/host/host-entry.js.map +0 -1
- package/dist/host/processes.js.map +0 -1
- package/dist/host/resolver-entry.js.map +0 -1
- package/dist/host/rpc-bridge.js.map +0 -1
- package/dist/host/rpc-executor.js.map +0 -1
- package/dist/host/runner.js.map +0 -1
- package/dist/host/state.js.map +0 -1
- package/dist/host/view.js.map +0 -1
- package/dist/host/worker-entry.js.map +0 -1
- package/dist/host/worker-protocol.d.ts +0 -46
- package/dist/host/worker-protocol.js +0 -122
- package/dist/host/worker-protocol.js.map +0 -1
- package/dist/host/worker-store.js.map +0 -1
- package/dist/host/worker-supervisor.d.ts +0 -22
- package/dist/host/worker-supervisor.js.map +0 -1
- package/docs/CONTROLLERS.md +0 -217
- package/src/controllers/definition.ts +0 -65
- package/src/controllers/errors.ts +0 -27
- package/src/controllers/index.ts +0 -88
- package/src/controllers/loader.ts +0 -104
- package/src/extension/controller-command.ts +0 -45
- package/src/host/controller-worker-protocol.ts +0 -104
- package/src/host/controller-worker-supervisor.ts +0 -79
- package/src/host/worker-protocol.ts +0 -176
- package/src/host/worker-supervisor.ts +0 -74
- /package/dist/{controllers → resource-managers}/json.d.ts +0 -0
- /package/dist/{controllers → resource-managers}/json.js +0 -0
- /package/dist/{controllers → resource-managers}/results.js +0 -0
- /package/dist/{controllers → resource-managers}/store.js +0 -0
- /package/dist/{controllers → resource-managers}/types.js +0 -0
- /package/dist/{host → server}/channel-effects.d.ts +0 -0
- /package/dist/{host → server}/channel-effects.js +0 -0
- /package/dist/{host/host-entry.d.ts → server/server-entry.d.ts} +0 -0
- /package/src/{controllers → resource-managers}/json.ts +0 -0
- /package/src/{host → server}/channel-effects.ts +0 -0
|
@@ -6,7 +6,7 @@ Status: this is the implemented workflow-message contract. [Unify workflow messa
|
|
|
6
6
|
|
|
7
7
|
Pi Workflows must add several kinds of content to an origin Pi conversation. These include interactive step prompts, protected human decisions, passive notifications, terminal results, and follow-up prompts. Initial, reminder, and resumed prompts are one step-message kind with different display reasons.
|
|
8
8
|
|
|
9
|
-
The
|
|
9
|
+
The server saves all of them as workflow messages. One extension component sends them through documented Pi APIs. Feature records continue to own workflow results, answers, settings, and timeouts.
|
|
10
10
|
|
|
11
11
|
The model receives complete instructions when a workflow message starts a turn. The user sees a compact card for structured workflow content and can expand it.
|
|
12
12
|
|
|
@@ -24,7 +24,7 @@ The message kinds are:
|
|
|
24
24
|
| `terminal` | Custom message that starts a model turn | Final result and safe recovery choice |
|
|
25
25
|
| `followUp` | Custom message that starts normal work | Work saved for after successful completion |
|
|
26
26
|
|
|
27
|
-
The
|
|
27
|
+
The server stores one `WorkflowMessage` record before Pi can send it:
|
|
28
28
|
|
|
29
29
|
```ts
|
|
30
30
|
type WorkflowMessage = {
|
|
@@ -45,9 +45,9 @@ type WorkflowMessage = {
|
|
|
45
45
|
|
|
46
46
|
The exact Pi content and custom display details remain in the content-addressed value store. `contentDigest` binds the record to those bytes. `sourceId` links the message to the feature record that created it.
|
|
47
47
|
|
|
48
|
-
`kind` determines the custom renderer, whether a model turn starts, and the
|
|
48
|
+
`kind` determines the custom renderer, whether a model turn starts, and the server eligibility rule. The server does not store duplicate flags or message-to-message pointers for those facts.
|
|
49
49
|
|
|
50
|
-
`order` is the acceptance order for one origin session. The
|
|
50
|
+
`order` is the acceptance order for one origin session. The server marks one pending item as next in the origin-session view. An earlier ineligible or cancelled item does not block unrelated eligible work.
|
|
51
51
|
|
|
52
52
|
A source lifecycle transaction can change `pending` to `cancelled`. Active-branch evidence changes `pending` or `cancelled` to `sent`; evidence wins because Pi already contains the entry. `sent` is terminal. A pending message can be new or uncertain after a process stopped, so the coordinator always checks the active branch before sending it.
|
|
53
53
|
|
|
@@ -118,26 +118,26 @@ Notifications keep the custom type `pi-workflows-notification` and use `triggerT
|
|
|
118
118
|
|
|
119
119
|
## One sender
|
|
120
120
|
|
|
121
|
-
The extension has one `WorkflowMessageCoordinator` for all message kinds. The
|
|
121
|
+
The extension has one `WorkflowMessageCoordinator` for all message kinds. The server keeps one active coordinator connection and process-local epoch for each origin session. A replacement connection fences the old one, so two Pi processes cannot send for the same session.
|
|
122
122
|
|
|
123
123
|
The coordinator follows this sequence:
|
|
124
124
|
|
|
125
|
-
1. After every
|
|
126
|
-
2. Wait until the
|
|
125
|
+
1. After every server connection, wait for the complete origin-session view and report the active branch before any send or turn report.
|
|
126
|
+
2. Wait until the server view names the next eligible pending message, Pi is idle, and Pi has no queued user input.
|
|
127
127
|
3. Save the message ID in the coordinator's queued map.
|
|
128
128
|
4. Search the active Pi branch for the same hidden message ID and report a matching entry.
|
|
129
129
|
5. Recheck synchronously that Pi is idle, has no pending input, the message is absent, and this connection still owns the active session epoch.
|
|
130
130
|
6. Call `pi.sendMessage()` with no `await` between that final check and call.
|
|
131
131
|
7. Wait until the new Pi entry is visible.
|
|
132
|
-
8. Report the active branch so the
|
|
132
|
+
8. Report the active branch so the server saves its Pi entry ID and marks the message `sent`.
|
|
133
133
|
|
|
134
134
|
The coordinator sends only one workflow message at a time. A poll can find work, but it cannot send an ID already in its queued map.
|
|
135
135
|
|
|
136
|
-
Pi can emit `agent_start` before the
|
|
136
|
+
Pi can emit `agent_start` before the server saves the new Pi entry ID. The coordinator keeps that start and any matching end in its in-memory session map. It first marks the workflow message `sent`, then reports the saved turn events in order. It does not drop a turn event while server confirmation is in progress.
|
|
137
137
|
|
|
138
138
|
A visible active-branch entry with the hidden ID is `sent`, even if the source lifecycle cancelled the message before the evidence arrived. Active-branch absence is usable only when the branch has no matching ID, Pi is idle, and Pi has no pending messages in the same observation. If Pi or the extension disappears after the send call, the message stays `pending`; reconnect reports the branch before another send. These rules do not prove cross-branch absence or exactly-once model execution.
|
|
139
139
|
|
|
140
|
-
After Pi, the extension, or the
|
|
140
|
+
After Pi, the extension, or the server restarts, branch reporting runs before any new send. It re-creates a message of the source's own kind only when the active branch has no entry for that source. A pending interaction gets one `step` with reason `resumed`; a pending decision gets one `decision`. Old incompatible workflow state is not reinterpreted.
|
|
141
141
|
|
|
142
142
|
## Model-turn status
|
|
143
143
|
|
|
@@ -149,13 +149,13 @@ After Pi, the extension, or the host restarts, branch reporting runs before any
|
|
|
149
149
|
|
|
150
150
|
A start against a closed message is rejected. Follow-up turns are reported for ordering but do not show the completed workflow as `running`.
|
|
151
151
|
|
|
152
|
-
The extension creates one workflow turn ID at `agent_start` and keeps it through the matching `agent_end` and
|
|
152
|
+
The extension creates one workflow turn ID at `agent_start` and keeps it through the matching `agent_end` and server reconnect. It buffers starts and ends until the session view and message receipt are ready.
|
|
153
153
|
|
|
154
|
-
At `agent_end`, it derives `completed`, `aborted`, or `error` from the documented assistant messages. It reads response-entry evidence from `ctx.sessionManager.getBranch()`; the entry ID can be null. The
|
|
154
|
+
At `agent_end`, it derives `completed`, `aborted`, or `error` from the documented assistant messages. It reads response-entry evidence from `ctx.sessionManager.getBranch()`; the entry ID can be null. The server saves one immutable end result. A repeated report adopts it, and a stale turn ID cannot clear newer activity. A supervised runner can continue after accepted tool output while that Pi turn is still open. This normal continuation leaves the session capture recording until `agent_end`; it does not mark the capture as interrupted.
|
|
155
155
|
|
|
156
|
-
The
|
|
156
|
+
The server applies the end and its workflow consequence in one transaction. An aborted turn sets the run pause and cancels the request's pending step messages; the interaction derives its paused state from the run. Resuming that submitted-output step atomically clears the pause, increments the interaction revision, and creates one new step message with reason `resumed`. That message starts a fresh Pi model turn. A protected decision does not start a model turn, so its revision and decision message do not change when its run resumes. A completed, recoverably failed, or proved-lost turn increments `unproductiveTurnEnds` only when the submitted-output step is pending, not paused, and has no accepted or validating submission. Values one and two create one step message with reason `reminder`; a value above two fails the attempt. A partial unique index enforces at most one pending step message for the request, regardless of reason. Acceptance, pause, cancel, timeout, and branch re-presentation cancel all pending step messages. A cancelled message did not start a turn and does not increment the counter.
|
|
157
157
|
|
|
158
|
-
There is no activity heartbeat, refresh lease, or sequence counter. The
|
|
158
|
+
There is no activity heartbeat, refresh lease, or sequence counter. The server shows `running` from the matching start until the matching end. Server startup never marks a Pi turn lost. On `session_start`, only an idle-session branch report can close an open sent message with synthetic stop reason `lost`. Polling and time alone cannot create a reminder.
|
|
159
159
|
|
|
160
160
|
## Feature ownership
|
|
161
161
|
|
|
@@ -190,9 +190,9 @@ A terminal or follow-up message does not reopen the completed workflow. A slash-
|
|
|
190
190
|
|
|
191
191
|
## Session recording
|
|
192
192
|
|
|
193
|
-
The extension records workflow-related Pi events through a batched
|
|
193
|
+
The extension records workflow-related Pi events through a batched server client operation. It uses documented Pi events and does not read or edit Pi session files.
|
|
194
194
|
|
|
195
|
-
The
|
|
195
|
+
The server deduplicates settled entries by Pi entry ID. It links attempts to their prompt, response, first, and last entries. A recording failure does not fail workflow execution.
|
|
196
196
|
|
|
197
197
|
## Public API boundary
|
|
198
198
|
|
|
@@ -214,14 +214,14 @@ Tests must prove:
|
|
|
214
214
|
- messages remain in saved order without message-pointer deadlocks;
|
|
215
215
|
- an early `agent_start` and `agent_end` wait for the message receipt and session view, then apply in order;
|
|
216
216
|
- every matching model turn shows `running` for its full duration;
|
|
217
|
-
- normal
|
|
217
|
+
- normal runner continuation leaves the active session capture open until the matching turn ends;
|
|
218
218
|
- stale turn-end reports and starts against closed messages are rejected;
|
|
219
219
|
- a manual turn cancels pending step messages that it supersedes;
|
|
220
220
|
- a turn cannot bind to an interaction whose run is paused;
|
|
221
221
|
- aborted turns pause without incrementing the unproductive-turn counter;
|
|
222
222
|
- resuming an aborted step creates one new resumed message and one fresh model turn;
|
|
223
223
|
- resuming a protected decision keeps its answer revision and does not create a duplicate decision message;
|
|
224
|
-
-
|
|
224
|
+
- server restart does not close a live Pi turn, while an idle-session branch report can close an unended turn as lost;
|
|
225
225
|
- two reminder-reason steps are sent at most once and the next unproductive turn fails;
|
|
226
226
|
- initial, reminder, and resumed prompts use the same step kind and differ only by reason;
|
|
227
227
|
- terminal and follow-up messages each start only at their durable boundary;
|
package/docs/WORKFLOW_UPDATES.md
CHANGED
|
@@ -11,10 +11,10 @@ const processRows = action({
|
|
|
11
11
|
run: async (context) => {
|
|
12
12
|
await context.publishUpdate({
|
|
13
13
|
type: "progress",
|
|
14
|
-
key: "
|
|
14
|
+
key: "runner-a",
|
|
15
15
|
data: {
|
|
16
16
|
schema: "pi-workflows.progress.v1",
|
|
17
|
-
label: "
|
|
17
|
+
label: "Runner A",
|
|
18
18
|
status: "running",
|
|
19
19
|
phase: "processing",
|
|
20
20
|
completed: 420,
|
|
@@ -37,10 +37,10 @@ An agent publishes the same update through the existing `workflow` tool:
|
|
|
37
37
|
"attempt": "017f5d57-83f1-4d2d-88e6-3dbf878fed17",
|
|
38
38
|
"update": {
|
|
39
39
|
"type": "progress",
|
|
40
|
-
"key": "
|
|
40
|
+
"key": "runner-a",
|
|
41
41
|
"data": {
|
|
42
42
|
"schema": "pi-workflows.progress.v1",
|
|
43
|
-
"label": "
|
|
43
|
+
"label": "Runner A",
|
|
44
44
|
"status": "running",
|
|
45
45
|
"completed": 420,
|
|
46
46
|
"total": 1000,
|
|
@@ -226,7 +226,7 @@ Shell actions may define a line parser:
|
|
|
226
226
|
|
|
227
227
|
```ts
|
|
228
228
|
shell({
|
|
229
|
-
exec: () => ({ command: "
|
|
229
|
+
exec: () => ({ command: "runner", args: ["--progress=ndjson"] }),
|
|
230
230
|
updates: {
|
|
231
231
|
streams: ["stdout"],
|
|
232
232
|
parseLine: ({ stream, text }, context) => {
|
|
@@ -234,7 +234,7 @@ shell({
|
|
|
234
234
|
if (value.kind !== "progress") return undefined;
|
|
235
235
|
return {
|
|
236
236
|
type: "progress",
|
|
237
|
-
key: value.
|
|
237
|
+
key: value.runner,
|
|
238
238
|
data: value.data,
|
|
239
239
|
};
|
|
240
240
|
},
|
|
@@ -248,11 +248,11 @@ The runtime keeps normal stdout and stderr capture. It applies backpressure whil
|
|
|
248
248
|
|
|
249
249
|
The parser is workflow-author code. Update data never selects or changes the command, arguments, working directory, environment, or privileges.
|
|
250
250
|
|
|
251
|
-
## Controllers and
|
|
251
|
+
## Controllers and servers
|
|
252
252
|
|
|
253
|
-
The engine exposes the same publication operation through its runner interface. A
|
|
253
|
+
The engine exposes the same publication operation through its runner interface. A resource manager or standalone server may publish only for a workflow run and attempt whose claim it owns.
|
|
254
254
|
|
|
255
|
-
|
|
255
|
+
Managed resource events remain resource manager events. They become workflow updates only when a resource manager deliberately publishes them to a linked workflow run.
|
|
256
256
|
|
|
257
257
|
## Persistence
|
|
258
258
|
|
|
@@ -271,7 +271,7 @@ Each accepted update transaction inserts one `workflow_updates` row and one
|
|
|
271
271
|
"payload": {
|
|
272
272
|
"updateId": "upd_01K2GZVY3A4D7X8J9M0N",
|
|
273
273
|
"type": "progress",
|
|
274
|
-
"key": "
|
|
274
|
+
"key": "runner-a",
|
|
275
275
|
"data": {
|
|
276
276
|
"schema": "pi-workflows.progress.v1",
|
|
277
277
|
"status": "running",
|
|
@@ -306,11 +306,11 @@ Default safety limits per run are:
|
|
|
306
306
|
|
|
307
307
|
The rate limiter uses a token bucket. A rejected update is not queued silently. Agent callers receive a tool error; code publishers receive a rejected promise.
|
|
308
308
|
|
|
309
|
-
The standalone
|
|
309
|
+
The standalone server and Pi extension apply the same limits.
|
|
310
310
|
|
|
311
311
|
## Progress update type
|
|
312
312
|
|
|
313
|
-
`progress` is the standard optional profile for measurable work. Each key represents one process,
|
|
313
|
+
`progress` is the standard optional profile for measurable work. Each key represents one process, runner, phase owner, or explicit overall total.
|
|
314
314
|
|
|
315
315
|
Minimal progress data:
|
|
316
316
|
|
|
@@ -326,7 +326,7 @@ Measured progress:
|
|
|
326
326
|
```json
|
|
327
327
|
{
|
|
328
328
|
"schema": "pi-workflows.progress.v1",
|
|
329
|
-
"label": "
|
|
329
|
+
"label": "Runner A",
|
|
330
330
|
"status": "running",
|
|
331
331
|
"phase": "processing",
|
|
332
332
|
"completed": 420,
|
|
@@ -363,7 +363,7 @@ Unknown fields in `pi-workflows.progress.v1` are validation errors.
|
|
|
363
363
|
|
|
364
364
|
## Progress estimation
|
|
365
365
|
|
|
366
|
-
The workflows layer exports pure validation and estimation helpers. Reduction and formatting use the same module. The helpers import no Pi, extension,
|
|
366
|
+
The workflows layer exports pure validation and estimation helpers. Reduction and formatting use the same module. The helpers import no Pi, extension, resource manager, or viewer code.
|
|
367
367
|
|
|
368
368
|
The estimator groups records by run and key. A new estimation epoch starts when:
|
|
369
369
|
|
|
@@ -402,7 +402,7 @@ The compact widget:
|
|
|
402
402
|
A typical line is:
|
|
403
403
|
|
|
404
404
|
```text
|
|
405
|
-
|
|
405
|
+
Runner A 420/1,000 rows ETA 18–20m
|
|
406
406
|
```
|
|
407
407
|
|
|
408
408
|
A footer line may show:
|
|
@@ -445,4 +445,4 @@ Updates are data. They do not grant permission to execute a command, retry work,
|
|
|
445
445
|
|
|
446
446
|
SQLite runs are private and may contain update data from external systems. The database permission and export warnings in [SQLite state](SQLITE_STATE.md) apply.
|
|
447
447
|
|
|
448
|
-
This feature does not add remote transports, a metrics database, global aggregation, automatic polling, or a new Pi core API. Workflow authors remain responsible for the trust and cost of their agent, action, shell, and
|
|
448
|
+
This feature does not add remote transports, a metrics database, global aggregation, automatic polling, or a new Pi core API. Workflow authors remain responsible for the trust and cost of their agent, action, shell, and resource manager code.
|
package/docs/development.md
CHANGED
|
@@ -10,22 +10,22 @@ The viewer follows the [incremental and virtualized viewer plan](plans/2026-08-2
|
|
|
10
10
|
```
|
|
11
11
|
src/workflows/ finite graph engine: definitions, execution, SQLite stores, loader
|
|
12
12
|
src/builtins/ default workflows shipped at lowest discovery precedence
|
|
13
|
-
src/
|
|
14
|
-
src/
|
|
13
|
+
src/resource-managers/ durable resources, queue, reconciliation, effects, child runs
|
|
14
|
+
src/server/ global server, local protocols, resolvers, and runner supervision
|
|
15
15
|
src/extension/ thin Pi client: commands, workflow tool, and origin-session bridge
|
|
16
|
-
src/viewer/ standalone read-only views over runs and
|
|
16
|
+
src/viewer/ standalone read-only views over runs and managed resources
|
|
17
17
|
tui/ Rust piw viewer, bounded SQLite projection, and replay server
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
The dependency direction is enforced by `slophammer.yml`. `src/workflows`
|
|
21
21
|
imports nothing outside itself and never imports Pi. Durable updates,
|
|
22
22
|
progress validation, estimation, and text formatting stay in this layer so the
|
|
23
|
-
engine, extension,
|
|
23
|
+
engine, extension, servers, and viewers share one contract. `src/builtins` contains
|
|
24
24
|
package-owned definitions and imports only the public workflow engine.
|
|
25
|
-
`src/
|
|
26
|
-
scheduling. `src/
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
`src/resource-managers` may import the public workflow engine for child-run
|
|
26
|
+
scheduling. `src/server` may import resource manager stores and workflow stores,
|
|
27
|
+
but the server event loop never loads workflow or resource manager definitions.
|
|
28
|
+
Resolver and runner child entry points load those definitions. The extension uses static
|
|
29
29
|
built-in metadata and never imports the workflow engine. The extension and
|
|
30
30
|
viewer never import each other. The viewer reads SQLite state in read-only mode,
|
|
31
31
|
so it works from any process.
|
|
@@ -64,12 +64,13 @@ line, misplaces an arrow, or lets a label damage an edge, those tests fail
|
|
|
64
64
|
with the offending drawing in the assertion message.
|
|
65
65
|
|
|
66
66
|
Inside the engine, the model-facing seam is the `AgentStepExecutor` interface.
|
|
67
|
-
The production
|
|
68
|
-
and `RpcStepExecutor` for headless
|
|
69
|
-
`
|
|
67
|
+
The production workflow runner uses an interaction executor for origin-session
|
|
68
|
+
work and `RpcStepExecutor` for headless resource manager work. The runner
|
|
69
|
+
receives a `ServerBackedWorkflowStore`; it cannot open a writable production
|
|
70
|
+
store.
|
|
70
71
|
|
|
71
72
|
The Pi extension uses documented session lifecycle, message, tool, and session
|
|
72
|
-
manager APIs. It writes no Pi session file. It saves
|
|
73
|
+
manager APIs. It writes no Pi session file. It saves server mutations through the
|
|
73
74
|
local protocol and uses the public session branch only to adopt the exact
|
|
74
75
|
visible entry for a durable interaction request. Tests can still use scripted
|
|
75
76
|
executors through the Pi-agnostic engine API.
|
|
@@ -135,9 +136,9 @@ the real pi CLI from `devDependencies` in RPC mode with:
|
|
|
135
136
|
- `HOME` pointed at a temporary home containing the canonical workflow database,
|
|
136
137
|
- the extension loaded from source with `-e src/extension/index.ts`.
|
|
137
138
|
|
|
138
|
-
It drives `/workflow` over the RPC protocol and checks the global
|
|
139
|
+
It drives `/workflow` over the RPC protocol and checks the global server,
|
|
139
140
|
durable interaction request, origin-session presentation, accepted submission,
|
|
140
|
-
|
|
141
|
+
runner recovery, final SQLite state, and viewer output. It also restarts the
|
|
141
142
|
real Pi process while one request is pending and proves that the same request is
|
|
142
143
|
not inserted twice. Nothing outside the temp directories is touched, and no
|
|
143
144
|
real model is called.
|
|
@@ -4,7 +4,7 @@ Pi Workflows uses one live client protocol for the Pi extension, the TypeScript
|
|
|
4
4
|
|
|
5
5
|
The protocol ID is `pi-workflows.client.v1`. Its schema is [`protocol/client.v1.schema.json`](../protocol/client.v1.schema.json). TypeScript and Rust use the same valid and invalid fixture corpus.
|
|
6
6
|
|
|
7
|
-
The
|
|
7
|
+
The server is the only process that reads or writes the active SQLite database. A client protocol or package-version mismatch does not mean that SQLite state is incompatible. The client stops and asks for matching `pi-workflows` and `piw` packages.
|
|
8
8
|
|
|
9
9
|
## Transports
|
|
10
10
|
|
|
@@ -14,11 +14,11 @@ The local transport is a user-only Unix socket on Unix systems:
|
|
|
14
14
|
~/.pi/agent/workflows/host/host.sock
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
On Windows, the same client uses the package-derived `\\.\pipe\pi-workflows-<state-directory-hash>` named pipe. Local `piw`, `piw serve`, and the TypeScript client derive the same endpoint from the workflow state directory. A foreground TypeScript cold start keeps its retry wait referenced until the detached
|
|
17
|
+
On Windows, the same client uses the package-derived `\\.\pipe\pi-workflows-<state-directory-hash>` named pipe. Local `piw`, `piw serve`, and the TypeScript client derive the same endpoint from the workflow state directory. A foreground TypeScript cold start keeps its retry wait referenced until the detached server becomes ready or the start deadline expires. Only background reconnect timers are unreferenced.
|
|
18
18
|
|
|
19
19
|
Each message is one canonical JSON object followed by a newline. TypeScript and Rust use the same ECMAScript number formatting and UTF-16 object-key order, including for arbitrary workflow JSON. A message can be at most 1 MiB. Unknown envelope fields, non-canonical JSON, and invalid framing close only the offending connection. If socket backpressure delays a client write, connection close, socket error, or request cancellation ends the wait instead of leaving the request pending.
|
|
20
20
|
|
|
21
|
-
`piw serve` provides the remote transport at `/ws`. It binds to loopback only. Each WebSocket connection has one matching
|
|
21
|
+
`piw serve` provides the remote transport at `/ws`. It binds to loopback only. Each WebSocket connection has one matching server-socket connection, and their lifecycles are coupled. The relay forwards one canonical JSON object per text frame. It does not read SQLite, translate views, multiplex clients, or retain state.
|
|
22
22
|
|
|
23
23
|
Remote clients use an SSH tunnel to reach the loopback relay.
|
|
24
24
|
|
|
@@ -31,7 +31,7 @@ The protocol has four message types:
|
|
|
31
31
|
- `response` settles one request with an outcome, optional revision, receipt, or safe error.
|
|
32
32
|
- `event` carries a revisioned run list, run snapshot, run patch, run page, session snapshot, or unavailable condition.
|
|
33
33
|
|
|
34
|
-
The
|
|
34
|
+
The server sends `hello` first:
|
|
35
35
|
|
|
36
36
|
```json
|
|
37
37
|
{
|
|
@@ -48,7 +48,7 @@ A request uses a stable request ID and idempotency key:
|
|
|
48
48
|
{
|
|
49
49
|
"clientId": "client-1",
|
|
50
50
|
"idempotencyKey": "status-1",
|
|
51
|
-
"operation": "
|
|
51
|
+
"operation": "server.status",
|
|
52
52
|
"payload": {},
|
|
53
53
|
"requestId": "request-1",
|
|
54
54
|
"schema": "pi-workflows.client.v1",
|
|
@@ -60,7 +60,7 @@ The closed outcomes are `accepted`, `adopted`, `rejected`, `conflict`, `notFound
|
|
|
60
60
|
|
|
61
61
|
## Run and session views
|
|
62
62
|
|
|
63
|
-
The
|
|
63
|
+
The server produces `pi-workflows.run-view.v1` from one consistent database read. The view contains the bounded workflow state, graph, trace, session projection, page cursors, presentation revision, and one `display` object. A terminal display includes the stored failure reason, not only its machine error code. A reason above the shared 16 KiB inline-content threshold uses a small `reason` notice and a digest-bound `reasonContent` reference. This keeps the run list below the 1 MiB frame limit and keeps the complete diagnostic available.
|
|
64
64
|
|
|
65
65
|
The closed display statuses are:
|
|
66
66
|
|
|
@@ -74,44 +74,44 @@ The closed display statuses are:
|
|
|
74
74
|
- `cancelled`
|
|
75
75
|
- `ambiguous`
|
|
76
76
|
|
|
77
|
-
Only the
|
|
77
|
+
Only the server computes this status. A parked queue is not a pause. `paused` requires the durable pause flag. An exact live runner or origin-session turn is `running`. An effect being applied by that live runner is still `running`; only a durable `ambiguous` effect that needs operator action is `ambiguous`. `unavailable` is a client connection condition, not a run status.
|
|
78
78
|
|
|
79
|
-
The run list uses the same display object. The
|
|
79
|
+
The run list uses the same display object. The server sends it as revision-bound `pi-workflows.run-list-page.v1` pages. Each page reads only lightweight run status and source facts. It does not load input, launch options, steps, trace, or session history. TypeScript and Rust clients collect all pages for one revision before they replace the visible list. If the revision changes, they discard the partial list and start from the next subscription event.
|
|
80
80
|
|
|
81
81
|
An origin-session subscription returns `pi-workflows.session-view.v1`. It contains the active run view first. When no run is active, it keeps the most recent terminal run while its terminal workflow message is pending or its first model turn is open, and then for 60 seconds after that turn ends. The same response contains an ordered byte-bounded window of all nonterminal workflow messages and open sent messages needed for recovery, their complete count, and the next eligible pending message only for the active coordinator epoch.
|
|
82
82
|
|
|
83
|
-
The Pi extension assembles the complete run revision and workflow-message content before it updates the widget or coordinator. After every
|
|
83
|
+
The Pi extension assembles the complete run revision and workflow-message content before it updates the widget or coordinator. After every server connection, it reports the active branch before it sends a workflow message or reports a model turn. The report can name only message IDs from the complete session-view window. The extension always uses the exact run ID from that view. It does not select the latest run separately. An idle session does not create empty commands.
|
|
84
84
|
|
|
85
85
|
A snapshot page contains at most 256 items and also has a byte budget. `view.page` returns another byte-bounded window that contains the requested cursor. Page responses use `pi-workflows.run-page.v1` and echo the requested cursor and run-view revision. A client applies a page only when both still match its current request and snapshot. A step-centered trace request selects the exact stored attempt first and uses its node only when that attempt has no trace event. Large workflow topology has bounded node, edge, graph-step, and transition projections plus durable content references for the complete original definition and complete graph history. TypeScript clients assemble every run-history page for that revision and hydrate the complete definition and all referenced content before they emit a complete non-interactive view or update the Pi widget. TypeScript and Rust use the same verified content loader for complete graph steps and transitions. Rust also requests, verifies, and decodes the complete referenced definition before it builds the graph layout. A session-event page also carries the replay checkpoint for the exact sequence before its first item, so reducing the page does not lose earlier active messages or tool calls. The checkpoint can itself be a durable content reference. TypeScript hydrates it with the run view, and Rust requests and resolves it before replay.
|
|
86
86
|
|
|
87
|
-
The
|
|
87
|
+
The server counts histories first and reads only the selected SQLite ranges. An unchanged subscription uses a lightweight revision check and reuses its prior view. It does not rebuild complete histories every 250 milliseconds.
|
|
88
88
|
|
|
89
|
-
Large prompt, output, event, settings, follow-up, and update values use a content reference instead of making a protocol frame exceed 1 MiB. `view.content` returns the referenced UTF-8 content in verified chunks. The reference includes its media type, byte count, SHA-256 digest, and an opaque marker for
|
|
89
|
+
Large prompt, output, event, settings, follow-up, and update values use a content reference instead of making a protocol frame exceed 1 MiB. `view.content` returns the referenced UTF-8 content in verified chunks. The reference includes its media type, byte count, SHA-256 digest, and an opaque marker for server-created references. The server saves generated view content directly under its exact run ID, content digest, and media type before it advertises the reference. It does not share media metadata with general state blobs. A request for another run or media representation is unavailable. Memory-cache eviction therefore cannot make an advertised aggregate unavailable. Run pruning removes the durable content. Clients reassemble all chunks and verify the bytes against both the content response and the advertised reference before display. Opaque content is restored as user data without interpreting nested objects as server references. Other cursors and content references keep the complete logical history and result available.
|
|
90
90
|
|
|
91
91
|
## Subscriptions and reconnection
|
|
92
92
|
|
|
93
|
-
A client keeps one persistent connection and records its desired run-list, run, and origin-session subscriptions. A request to watch a run that does not exist returns `notFound` and does not install a subscription. TypeScript and Rust clients show that response instead of waiting for a snapshot. Explicit `piw <runId>` mode keeps that run selected even when the run list contains a newer run. After reconnection, the client sends accepted subscriptions again with its run revision. The
|
|
93
|
+
A client keeps one persistent connection and records its desired run-list, run, and origin-session subscriptions. A request to watch a run that does not exist returns `notFound` and does not install a subscription. TypeScript and Rust clients show that response instead of waiting for a snapshot. Explicit `piw <runId>` mode keeps that run selected even when the run list contains a newer run. After reconnection, the client sends accepted subscriptions again with its run revision. The server sends a bounded snapshot when the client needs one. The protocol also supports retained revision patches. Unsubscribing sends the subscription ID to the server for every subscription kind, so no unused snapshot work remains on a live connection.
|
|
94
94
|
|
|
95
|
-
A slow or disconnected client cannot stop the
|
|
95
|
+
A slow or disconnected client cannot stop the server, another client, claim renewal, or workflow execution. The server waits for socket drain before it publishes another snapshot to that connection. Polling coalesces while the connection is blocked, so the socket buffer cannot grow by one snapshot on every poll. When a connection closes, the server removes its subscriptions and active coordinator epoch. It does not infer that an open model turn ended.
|
|
96
96
|
|
|
97
97
|
## Origin-session activity
|
|
98
98
|
|
|
99
|
-
The Pi extension uses `workflowTurn.report` to record `started` and `ended` for the exact workflow message, workflow turn, run, and origin session. There is no refresh, activity lease, heartbeat, or sequence counter. A started turn keeps the
|
|
99
|
+
The Pi extension uses `workflowTurn.report` to record `started` and `ended` for the exact workflow message, workflow turn, run, and origin session. There is no refresh, activity lease, heartbeat, or sequence counter. A started turn keeps the server-produced display status `running` until the matching end is accepted. A stale end cannot clear a newer turn.
|
|
100
100
|
|
|
101
|
-
The
|
|
101
|
+
The server accepts a start only for an open sent workflow message in that origin session. A step message is open while its interaction remains pending and its run is not paused. A terminal or follow-up message is open until its first turn ends. Decisions and notifications never open model turns. Turn binding does not inspect branch membership because documented `agent_start` has no message payload; branch reporting owns entry adoption and branch-specific re-presentation.
|
|
102
102
|
|
|
103
|
-
If the workflow message or session view is still loading, the extension buffers the matching start and end and reports them in order after the message is confirmed `sent`. Activity changes display only. It does not grant workflow authority, renew a run claim, or settle an interaction.
|
|
103
|
+
If the workflow message or session view is still loading, the extension buffers the matching start and end and reports them in order after the message is confirmed `sent`. Activity changes display only. It does not grant workflow authority, renew a run claim, or settle an interaction. Server startup does not close an open Pi turn. Only a later idle-session active-branch report can close a proved-lost turn.
|
|
104
104
|
|
|
105
105
|
## Commands and uncertain results
|
|
106
106
|
|
|
107
|
-
Durable commands use stable idempotency keys. The Pi extension routes each state-changing command through the durable client request path. If the connection closes after the
|
|
107
|
+
Durable commands use stable idempotency keys. The Pi extension routes each state-changing command through the durable client request path. If the connection closes after the server commits but before the response arrives, the client reconnects with a new request ID and adopts the stored result. A retry with the same durable identity and payload adopts the stored receipt. Reusing that identity with another payload is a conflict. The request ID identifies one transport attempt and is not part of the durable request fingerprint. A retry after a local abort uses a new request ID and keeps the durable idempotency and submission IDs, so a late response from the aborted attempt cannot settle the retry.
|
|
108
108
|
|
|
109
|
-
An `interaction.submit` response stays open while the supervised workflow child validates the value. The response settles only after the durable result is accepted, adopted, or rejected. A tool abort stops waiting immediately but does not undo an accepted
|
|
109
|
+
An `interaction.submit` response stays open while the supervised workflow child validates the value. The response settles only after the durable result is accepted, adopted, or rejected. A tool abort stops waiting immediately but does not undo an accepted server command. A later retry adopts the durable outcome. The server waits on the stored submission ID returned by adoption, not a different ID from the retry attempt. Clients do not poll SQLite.
|
|
110
110
|
|
|
111
111
|
The protocol does not claim exactly-once behavior for an external system that cannot prove it. An uncertain non-idempotent effect becomes ambiguous and requires explicit recovery.
|
|
112
112
|
|
|
113
113
|
## Maintenance
|
|
114
114
|
|
|
115
|
-
Active `state.status`, `state.verify`, `state.backup`, and `state.prune` requests run in the
|
|
115
|
+
Active `state.status`, `state.verify`, `state.backup`, and `state.prune` requests run in the server against its existing database connection. The CLI uses one stable client identity and creates a fresh idempotency key for each backup or applied prune invocation. If that invocation loses its connection, the client reconnects once with a new request ID and the same invocation key, so the server adopts the exact in-flight or stored result. A later user invocation gets a new key and does not reuse a stale success or rejection. The server keeps an in-flight maintenance command alive after a client disconnect, stores its accepted or rejected receipt before it responds, and adopts the exact retry instead of running the operation again. Server shutdown waits for in-flight maintenance receipts. `state.status` returns the database file size and safe counts for resources, runs, managed resources, decisions, settings scopes, pending interactions, pending follow-ups, active leases, and unsettled effects.
|
|
116
116
|
|
|
117
|
-
Only `pi-workflows state verify <inactive-backup>` opens SQLite outside the
|
|
117
|
+
Only `pi-workflows state verify <inactive-backup>` opens SQLite outside the server. It uses a query-only TypeScript connection and rejects the active database, including another path to the same file.
|
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: Add a durable
|
|
2
|
+
title: Add a durable resource manager runtime
|
|
3
3
|
author: Onur Solmaz <2453968+osolmaz@users.noreply.github.com>
|
|
4
4
|
date: 2026-08-04
|
|
5
5
|
updated: 2026-08-04
|
|
6
6
|
status: implemented
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
#
|
|
9
|
+
# ResourceManager runtime plan
|
|
10
10
|
|
|
11
|
-
pi-workflows needs a controller mode for automation that spans repeated events, external state changes, and process restarts. The design in [
|
|
11
|
+
pi-workflows needs a controller mode for automation that spans repeated events, external state changes, and process restarts. The design in [RESOURCE_MANAGERS.md](../RESOURCE_MANAGERS.md) follows the Kubernetes controller pattern. Durable resources hold desired and observed state, events enqueue resource keys, and each reconciliation reads current facts before acting.
|
|
12
12
|
|
|
13
13
|
The implementation keeps the graph engine focused on finite jobs. Controllers start and observe workflows through a child-run interface. Workflow graphs keep their finite execution model.
|
|
14
14
|
|
|
15
15
|
## Shipped design
|
|
16
16
|
|
|
17
|
-
The implementation follows [
|
|
17
|
+
The implementation follows [RESOURCE_MANAGERS.md](../RESOURCE_MANAGERS.md) with these resolved choices:
|
|
18
18
|
|
|
19
19
|
- The local store uses `better-sqlite3` because the Node 22 SQLite module still emits an experimental warning.
|
|
20
|
-
- Controllers are discovered from `.pi/
|
|
21
|
-
- The existing `pi-workflows` executable provides read-only `controllers` and `controller` commands. Headless workers use the public `
|
|
20
|
+
- Controllers are discovered from `.pi/resource-managers/` and `~/.pi/agent/resource-managers/`.
|
|
21
|
+
- The existing `pi-workflows` executable provides read-only `controllers` and `controller` commands. Headless workers use the public `ResourceManagerRuntime` API.
|
|
22
22
|
- The TypeScript CLI lists and inspects resources. The Rust viewer understands interrupted workflow runs. Resource views stay in the CLI for this release.
|
|
23
|
-
- Per-controller concurrency limits are manager configuration, while
|
|
23
|
+
- Per-controller concurrency limits are manager configuration, while resource manager definitions contain reconciliation behavior and timeout only.
|
|
24
24
|
|
|
25
25
|
The persisted model was reviewed with Schemator before implementation. The review removed generic timestamps from public resources, kept provider details opaque, moved concurrency policy to the manager, and tightened event payloads to recursive JSON values. The run failed aggregate validation on two proposed structural moves, so those moves received a manual review instead of automatic application.
|
|
26
26
|
|
|
@@ -28,7 +28,7 @@ The persisted model was reviewed with Schemator before implementation. The revie
|
|
|
28
28
|
|
|
29
29
|
The first release must provide:
|
|
30
30
|
|
|
31
|
-
- Typed
|
|
31
|
+
- Typed resource manager definitions and durable resources with `spec`, `status`, generations and conditions, plus compare-and-swap versions.
|
|
32
32
|
- A durable keyed queue with deduplication, delayed wakeups, bounded concurrency, retry backoff, and expiring claims.
|
|
33
33
|
- Level-based reconciliation that receives a resource instead of an event payload.
|
|
34
34
|
- Effect records that recover safely when a process stops around an external mutation.
|
|
@@ -43,41 +43,41 @@ The default controller store will use SQLite and local filesystem permissions th
|
|
|
43
43
|
|
|
44
44
|
The first release will support several workers in one process. Queue claims will survive process failure, but cross-host leader election will wait for a real remote deployment.
|
|
45
45
|
|
|
46
|
-
Existing workflow definitions and run bundles remain valid.
|
|
46
|
+
Existing workflow definitions and run bundles remain valid. ResourceManager resources use a separate schema and store. Child workflow attempts use the current run-bundle format; their parent links live in the controller store.
|
|
47
47
|
|
|
48
48
|
## Open questions
|
|
49
49
|
|
|
50
50
|
Implementation should resolve these points before the public API is frozen:
|
|
51
51
|
|
|
52
52
|
- Confirm that the built-in Node SQLite API meets package portability and transaction requirements on every supported Node 22 release. Choose one documented dependency if it does not.
|
|
53
|
-
- Decide whether controller discovery belongs in `.pi/
|
|
53
|
+
- Decide whether controller discovery belongs in `.pi/resource-managers/` and `~/.pi/agent/resource-managers/`, or whether controllers should be registered only from installed packages.
|
|
54
54
|
- Decide whether the existing `pi-workflows` executable should gain controller subcommands or whether the package should expose another binary.
|
|
55
55
|
- Set queue claim and retry defaults from local fault tests. Use the same evidence for retention and payload limits.
|
|
56
56
|
- Define the smallest viewer change that makes blocked and indeterminate resources easy to find.
|
|
57
57
|
|
|
58
58
|
## Source layout
|
|
59
59
|
|
|
60
|
-
Add a `src/
|
|
60
|
+
Add a `src/resource-managers/` layer with no dependency on Pi:
|
|
61
61
|
|
|
62
62
|
```text
|
|
63
63
|
src/workflows/ finite graph execution
|
|
64
|
-
src/
|
|
65
|
-
src/
|
|
66
|
-
src/
|
|
67
|
-
src/
|
|
68
|
-
src/
|
|
69
|
-
src/
|
|
64
|
+
src/resource-managers/types.ts resources, conditions, results, definitions
|
|
65
|
+
src/resource-managers/store.ts store and queue interfaces
|
|
66
|
+
src/resource-managers/sqlite.ts local durable implementation
|
|
67
|
+
src/resource-managers/manager.ts claims, workers, retries, cancellation
|
|
68
|
+
src/resource-managers/effects.ts effect lifecycle and recovery
|
|
69
|
+
src/resource-managers/workflows.ts child workflow scheduler adapter
|
|
70
70
|
src/extension/ Pi commands and lifecycle hooks, plus UI
|
|
71
71
|
src/viewer/ read-only local views
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
-
`src/workflows` must never import `src/
|
|
74
|
+
`src/workflows` must never import `src/resource-managers`. The controller layer may depend on exported workflow contracts through `resource-managers/workflows.ts`. The extension and viewer may use both layers and remain independent from each other. Update `slophammer.yml` before adding cross-layer imports.
|
|
75
75
|
|
|
76
76
|
## Work stages
|
|
77
77
|
|
|
78
78
|
### Resource contract
|
|
79
79
|
|
|
80
|
-
Define the TypeScript API, persisted schemas, condition helpers, generation rules, and compare-and-swap behavior. Review the persisted model with Schemator before implementation. Document the final decisions in [
|
|
80
|
+
Define the TypeScript API, persisted schemas, condition helpers, generation rules, and compare-and-swap behavior. Review the persisted model with Schemator before implementation. Document the final decisions in [RESOURCE_MANAGERS.md](../RESOURCE_MANAGERS.md).
|
|
81
81
|
|
|
82
82
|
Add tests for spec updates, status-only writes, stale versions, condition transition times, deletion requests, and finalizer removal. A spec update must increase `generation`; a status update must preserve it.
|
|
83
83
|
|
|
@@ -89,11 +89,11 @@ Implement one queue row per controller and resource key. Enqueue must deduplicat
|
|
|
89
89
|
|
|
90
90
|
Run concurrent worker tests that force compare-and-swap conflicts and claim expiry. All tests must use temporary directories.
|
|
91
91
|
|
|
92
|
-
###
|
|
92
|
+
### ResourceManager manager
|
|
93
93
|
|
|
94
|
-
Implement `
|
|
94
|
+
Implement `defineResourceManager`, `ResourceManagerRuntime`, and the settled, immediate requeue, and delayed requeue results. Reconciliation requests carry a key; the manager reads the resource immediately before calling user code.
|
|
95
95
|
|
|
96
|
-
Enforce one active reconciliation per key. Apply returned status against the version that was read. A conflict discards the stale status and queues another pass. Add global and per-
|
|
96
|
+
Enforce one active reconciliation per key. Apply returned status against the version that was read. A conflict discards the stale status and queues another pass. Add global and per-resource runner limits.
|
|
97
97
|
|
|
98
98
|
Emit structured lifecycle records with stable reconcile IDs and bounded error messages. Cancellation must stop new claims and give active reconcilers a fixed shutdown period.
|
|
99
99
|
|
|
@@ -115,7 +115,7 @@ Test duplicate child requests, changed fingerprints, completion races, parent ge
|
|
|
115
115
|
|
|
116
116
|
### Extension and viewer
|
|
117
117
|
|
|
118
|
-
Add controller discovery and a `/
|
|
118
|
+
Add controller discovery and a `/resource-manager` command for listing resources, inspecting conditions, requesting reconciliation, and cancelling active local work. Use only documented Pi extension APIs.
|
|
119
119
|
|
|
120
120
|
Start local sources from `session_start` and close them idempotently during `session_shutdown`. Pi exit must leave durable resources and queue rows ready for another host. No background service is installed.
|
|
121
121
|
|
|
@@ -156,7 +156,7 @@ npx slophammer-ts@latest check . --only ts.dependency-boundaries-required
|
|
|
156
156
|
npx -y @simpledoc/simpledoc check
|
|
157
157
|
```
|
|
158
158
|
|
|
159
|
-
Exercise the Pi extension from source with `pi -e src/extension/index.ts`. Start a
|
|
159
|
+
Exercise the Pi extension from source with `pi -e src/extension/index.ts`. Start a managed resource, stop Pi while work is pending, reopen the same store, and confirm that reconciliation continues from current state. Repeat the effect crash matrix with the fake provider and preserve the test output as CI evidence.
|
|
160
160
|
|
|
161
161
|
## Non-goals
|
|
162
162
|
|
|
@@ -164,6 +164,6 @@ The first implementation will not provide Kubernetes API compatibility, a distri
|
|
|
164
164
|
|
|
165
165
|
## Documentation updates
|
|
166
166
|
|
|
167
|
-
Update [
|
|
167
|
+
Update [RESOURCE_MANAGERS.md](../RESOURCE_MANAGERS.md) whenever the public resource, queue, reconciliation, or effect contracts change. Add the shipped authoring surface to [workflows.md](../workflows.md), the source boundaries to [development.md](../development.md), and user-facing installation and commands to the root README after implementation.
|
|
168
168
|
|
|
169
169
|
Record any meaningful departure from this plan in this document before the implementation is considered complete.
|