@osolmaz/pi-workflows 0.13.4 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +146 -160
- package/dist/builtins/autoimplement.workflow.js +3 -1
- package/dist/builtins/autoimplement.workflow.js.map +1 -1
- package/dist/builtins/change-verification.workflow.js +7 -2
- package/dist/builtins/change-verification.workflow.js.map +1 -1
- package/dist/builtins/metadata.d.ts +22 -0
- package/dist/builtins/metadata.js +10 -0
- package/dist/builtins/metadata.js.map +1 -0
- package/dist/builtins/monitor.workflow.js +4 -1
- package/dist/builtins/monitor.workflow.js.map +1 -1
- package/dist/builtins/sanity-check.workflow.js +4 -1
- package/dist/builtins/sanity-check.workflow.js.map +1 -1
- package/dist/builtins/workspace-preparation.workflow.js +3 -1
- package/dist/builtins/workspace-preparation.workflow.js.map +1 -1
- package/dist/controllers/index.d.ts +1 -2
- package/dist/controllers/index.js +0 -1
- package/dist/controllers/index.js.map +1 -1
- package/dist/controllers/sqlite.d.ts +85 -31
- package/dist/controllers/sqlite.js +541 -126
- package/dist/controllers/sqlite.js.map +1 -1
- package/dist/controllers/store.d.ts +1 -0
- package/dist/extension/controller-command.d.ts +22 -0
- package/dist/extension/controller-command.js +32 -0
- package/dist/extension/controller-command.js.map +1 -0
- package/dist/extension/index.d.ts +2 -9
- package/dist/extension/index.js +876 -2928
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/message-card.d.ts +13 -0
- package/dist/extension/message-card.js +46 -0
- package/dist/extension/message-card.js.map +1 -0
- package/dist/extension/step-message.d.ts +5 -6
- package/dist/extension/step-message.js +41 -52
- package/dist/extension/step-message.js.map +1 -1
- package/dist/host/child-worker-supervisor.d.ts +50 -0
- package/dist/host/child-worker-supervisor.js +159 -0
- package/dist/host/child-worker-supervisor.js.map +1 -0
- package/dist/host/client.d.ts +48 -0
- package/dist/host/client.js +216 -0
- package/dist/host/client.js.map +1 -0
- package/dist/host/controller-worker-entry.d.ts +2 -0
- package/dist/host/controller-worker-entry.js +244 -0
- package/dist/host/controller-worker-entry.js.map +1 -0
- package/dist/host/controller-worker-protocol.d.ts +36 -0
- package/dist/host/controller-worker-protocol.js +49 -0
- package/dist/host/controller-worker-protocol.js.map +1 -0
- package/dist/host/controller-worker-supervisor.d.ts +21 -0
- package/dist/host/controller-worker-supervisor.js +54 -0
- package/dist/host/controller-worker-supervisor.js.map +1 -0
- package/dist/host/host-entry.d.ts +2 -0
- package/dist/host/host-entry.js +23 -0
- package/dist/host/host-entry.js.map +1 -0
- package/dist/host/processes.d.ts +17 -12
- package/dist/host/processes.js +154 -50
- package/dist/host/processes.js.map +1 -1
- package/dist/host/protocol.d.ts +38 -0
- package/dist/host/protocol.js +156 -0
- package/dist/host/protocol.js.map +1 -0
- package/dist/host/resolver-entry.d.ts +49 -0
- package/dist/host/resolver-entry.js +149 -0
- package/dist/host/resolver-entry.js.map +1 -0
- package/dist/host/rpc-executor.d.ts +14 -3
- package/dist/host/rpc-executor.js +63 -30
- package/dist/host/rpc-executor.js.map +1 -1
- package/dist/host/runner.d.ts +95 -29
- package/dist/host/runner.js +2311 -348
- package/dist/host/runner.js.map +1 -1
- package/dist/host/state.d.ts +174 -0
- package/dist/host/state.js +689 -0
- package/dist/host/state.js.map +1 -0
- package/dist/host/worker-entry.d.ts +10 -0
- package/dist/host/worker-entry.js +446 -0
- package/dist/host/worker-entry.js.map +1 -0
- package/dist/host/worker-protocol.d.ts +31 -0
- package/dist/host/worker-protocol.js +122 -0
- package/dist/host/worker-protocol.js.map +1 -0
- package/dist/host/worker-store.d.ts +80 -0
- package/dist/host/worker-store.js +91 -0
- package/dist/host/worker-store.js.map +1 -0
- package/dist/host/worker-supervisor.d.ts +22 -0
- package/dist/host/worker-supervisor.js +54 -0
- package/dist/host/worker-supervisor.js.map +1 -0
- package/dist/render/canvas.d.ts +2 -1
- package/dist/render/canvas.js +40 -15
- package/dist/render/canvas.js.map +1 -1
- package/dist/render/graph-render.d.ts +7 -3
- package/dist/render/graph-render.js +125 -74
- package/dist/render/graph-render.js.map +1 -1
- package/dist/state/database.js +3 -1
- package/dist/state/database.js.map +1 -1
- package/dist/state/index.d.ts +1 -0
- package/dist/state/index.js +1 -0
- package/dist/state/index.js.map +1 -1
- package/dist/state/prune.js +36 -10
- package/dist/state/prune.js.map +1 -1
- package/dist/state/schema.d.ts +1 -1
- package/dist/state/schema.js +171 -3
- package/dist/state/schema.js.map +1 -1
- package/dist/state/viewer.d.ts +46 -0
- package/dist/state/viewer.js +249 -0
- package/dist/state/viewer.js.map +1 -0
- package/dist/viewer/cli.d.ts +1 -1
- package/dist/viewer/cli.js +47 -18
- package/dist/viewer/cli.js.map +1 -1
- package/dist/workflows/composition.js +25 -2
- package/dist/workflows/composition.js.map +1 -1
- package/dist/workflows/definition.d.ts +3 -1
- package/dist/workflows/definition.js +25 -0
- package/dist/workflows/definition.js.map +1 -1
- package/dist/workflows/engine.d.ts +1 -0
- package/dist/workflows/engine.js +157 -42
- package/dist/workflows/engine.js.map +1 -1
- package/dist/workflows/errors.d.ts +3 -1
- package/dist/workflows/errors.js +4 -7
- package/dist/workflows/errors.js.map +1 -1
- package/dist/workflows/human-decision.d.ts +3 -0
- package/dist/workflows/human-decision.js +31 -0
- package/dist/workflows/human-decision.js.map +1 -1
- package/dist/workflows/index.d.ts +1 -1
- package/dist/workflows/index.js +1 -1
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/schema.js +17 -6
- package/dist/workflows/schema.js.map +1 -1
- package/dist/{viewer → workflows}/session-reducer.d.ts +3 -1
- package/dist/{viewer → workflows}/session-reducer.js +4 -0
- package/dist/workflows/session-reducer.js.map +1 -0
- package/dist/workflows/store.d.ts +87 -1
- package/dist/workflows/store.js +892 -93
- package/dist/workflows/store.js.map +1 -1
- package/dist/workflows/tool-input.d.ts +0 -22
- package/dist/workflows/tool-input.js +1 -44
- package/dist/workflows/tool-input.js.map +1 -1
- package/dist/workflows/types.d.ts +36 -4
- package/docs/2026-08-25-workflow-follow-ups.md +8 -6
- package/docs/2026-08-30-out-of-process-workflow-host-plan.md +353 -0
- package/docs/CONTROLLERS.md +11 -11
- package/docs/DEFERRED_TURNS.md +61 -27
- package/docs/HUMAN_DECISIONS.md +12 -4
- package/docs/SQLITE_STATE.md +42 -8
- package/docs/WORKFLOW_HOST.md +452 -0
- package/docs/development.md +46 -30
- package/docs/live-replay-protocol.md +129 -100
- package/docs/plans/2026-08-19-human-decision-gates-plan.md +34 -8
- package/docs/plans/2026-08-27-workflow-terminal-restart-plan.md +399 -0
- package/docs/plans/2026-08-28-piw-incremental-viewer-plan.md +293 -0
- package/docs/plans/piw-viewer-experience-implementation-plan.md +11 -1
- package/docs/plans/tui-viewer-implementation-plan.md +5 -0
- package/docs/tui-viewer.md +18 -3
- package/docs/workflows.md +164 -156
- package/examples/workflows/command-batch.workflow.ts +2 -0
- package/examples/workflows/shell.workflow.ts +2 -1
- package/herdr-plugin.toml +1 -1
- package/package.json +1 -1
- package/skills/autodoc/SKILL.md +1 -1
- package/skills/autoimplement/SKILL.md +1 -1
- package/skills/autoplan/SKILL.md +1 -1
- package/skills/pi-workflows/SKILL.md +2 -0
- package/src/builtins/autoimplement.workflow.ts +3 -0
- package/src/builtins/change-verification.workflow.ts +7 -2
- package/src/builtins/metadata.ts +9 -0
- package/src/builtins/monitor.workflow.ts +4 -0
- package/src/builtins/sanity-check.workflow.ts +4 -0
- package/src/builtins/workspace-preparation.workflow.ts +3 -1
- package/src/controllers/index.ts +3 -5
- package/src/controllers/sqlite.ts +929 -225
- package/src/controllers/store.ts +1 -0
- package/src/extension/controller-command.ts +45 -0
- package/src/extension/index.ts +948 -3506
- package/src/extension/message-card.ts +61 -0
- package/src/extension/step-message.ts +58 -63
- package/src/host/child-worker-supervisor.ts +183 -0
- package/src/host/client.ts +293 -0
- package/src/host/controller-worker-entry.ts +311 -0
- package/src/host/controller-worker-protocol.ts +104 -0
- package/src/host/controller-worker-supervisor.ts +79 -0
- package/src/host/host-entry.ts +23 -0
- package/src/host/processes.ts +171 -54
- package/src/host/protocol.ts +196 -0
- package/src/host/resolver-entry.ts +241 -0
- package/src/host/rpc-executor.ts +76 -34
- package/src/host/runner.ts +2813 -422
- package/src/host/state.ts +1160 -0
- package/src/host/worker-entry.ts +533 -0
- package/src/host/worker-protocol.ts +165 -0
- package/src/host/worker-store.ts +229 -0
- package/src/host/worker-supervisor.ts +74 -0
- package/src/render/canvas.ts +44 -10
- package/src/render/graph-render.ts +145 -90
- package/src/state/database.ts +2 -1
- package/src/state/index.ts +14 -0
- package/src/state/prune.ts +35 -9
- package/src/state/schema.ts +171 -3
- package/src/state/viewer.ts +356 -0
- package/src/viewer/cli.ts +49 -17
- package/src/workflows/composition.ts +36 -2
- package/src/workflows/definition.ts +32 -0
- package/src/workflows/engine.ts +157 -54
- package/src/workflows/errors.ts +11 -2
- package/src/workflows/human-decision.ts +49 -0
- package/src/workflows/index.ts +2 -0
- package/src/workflows/schema.ts +19 -6
- package/src/{viewer → workflows}/session-reducer.ts +11 -1
- package/src/workflows/store.ts +1316 -108
- package/src/workflows/tool-input.ts +2 -60
- package/src/workflows/types.ts +32 -4
- package/dist/controllers/workflow-engine-scheduler.d.ts +0 -29
- package/dist/controllers/workflow-engine-scheduler.js +0 -175
- package/dist/controllers/workflow-engine-scheduler.js.map +0 -1
- package/dist/extension/controller-host.d.ts +0 -48
- package/dist/extension/controller-host.js +0 -110
- package/dist/extension/controller-host.js.map +0 -1
- package/dist/extension/deferred-turn-coordinator.d.ts +0 -32
- package/dist/extension/deferred-turn-coordinator.js +0 -143
- package/dist/extension/deferred-turn-coordinator.js.map +0 -1
- package/dist/extension/deferred-turn.d.ts +0 -44
- package/dist/extension/deferred-turn.js +0 -110
- package/dist/extension/deferred-turn.js.map +0 -1
- package/dist/extension/executor.d.ts +0 -86
- package/dist/extension/executor.js +0 -311
- package/dist/extension/executor.js.map +0 -1
- package/dist/extension/follow-up-coordinator.d.ts +0 -27
- package/dist/extension/follow-up-coordinator.js +0 -131
- package/dist/extension/follow-up-coordinator.js.map +0 -1
- package/dist/extension/recorder.d.ts +0 -84
- package/dist/extension/recorder.js +0 -528
- package/dist/extension/recorder.js.map +0 -1
- package/dist/extension/session-events.d.ts +0 -133
- package/dist/extension/session-events.js +0 -61
- package/dist/extension/session-events.js.map +0 -1
- package/dist/viewer/session-reducer.js.map +0 -1
- package/src/controllers/workflow-engine-scheduler.ts +0 -246
- package/src/extension/controller-host.ts +0 -167
- package/src/extension/deferred-turn-coordinator.ts +0 -171
- package/src/extension/deferred-turn.ts +0 -166
- package/src/extension/executor.ts +0 -411
- package/src/extension/follow-up-coordinator.ts +0 -151
- package/src/extension/recorder.ts +0 -655
- package/src/extension/session-events.ts +0 -121
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
# Workflow host
|
|
2
|
+
|
|
3
|
+
Status: implemented. [Run workflows outside Pi](2026-08-30-out-of-process-workflow-host-plan.md) records the approved implementation plan.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
The workflow host keeps durable workflow state correct when Pi, a workflow, or the host stops unexpectedly. It owns the workflow database and supervises a separate process for each active run. Pi remains the user interface and performs interactive model turns through its documented extension APIs.
|
|
8
|
+
|
|
9
|
+
The host solves two different failures:
|
|
10
|
+
|
|
11
|
+
- A busy workflow cannot block the process that renews run claims.
|
|
12
|
+
- A crashed or stale runner cannot leave contradictory state or continue writing after another runner takes over.
|
|
13
|
+
|
|
14
|
+
## Terms
|
|
15
|
+
|
|
16
|
+
- **Host:** The single user-level process that owns workflow state, claims, commands, and worker supervision.
|
|
17
|
+
- **Client:** A Pi extension instance or command-line process connected to the host.
|
|
18
|
+
- **Worker:** A child process that loads one workflow and executes one active run generation.
|
|
19
|
+
- **Origin session:** The Pi session that started an interactive run.
|
|
20
|
+
- **Claim:** A time-limited right to change one run.
|
|
21
|
+
- **Generation:** A number increased each time a new owner claims a run. It fences older owners.
|
|
22
|
+
- **Durable boundary:** A committed node or lifecycle transition from which execution can resume.
|
|
23
|
+
- **Interactive request:** A durable agent or assistant-message step that must run in the origin Pi session.
|
|
24
|
+
- **Managed effect:** A side effect reserved and settled through an idempotent durable record.
|
|
25
|
+
|
|
26
|
+
## Boundaries
|
|
27
|
+
|
|
28
|
+
The host belongs to the `@osolmaz/pi-workflows` package. It uses the existing SQLite database at `~/.pi/agent/workflows/state.sqlite` and documented Pi extension APIs.
|
|
29
|
+
|
|
30
|
+
The design does not change Pi source, Pi session files, Pi message schemas, or private Pi APIs. It does not add a remote service or a second database. The package does not install an operating-system service.
|
|
31
|
+
|
|
32
|
+
SQLite remains local to one machine. The protocol does not provide distributed consensus or network-filesystem safety.
|
|
33
|
+
|
|
34
|
+
## Process model
|
|
35
|
+
|
|
36
|
+
One host owns the global workflow database for one user installation.
|
|
37
|
+
|
|
38
|
+
```text
|
|
39
|
+
Pi extension ─┐
|
|
40
|
+
Pi extension ─┼── local socket ── workflow host ── SQLite
|
|
41
|
+
CLI client ───┘ │
|
|
42
|
+
├── run worker A
|
|
43
|
+
├── run worker B ── headless pi --mode rpc
|
|
44
|
+
├── controller worker
|
|
45
|
+
└── source resolver
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The host may manage runs from more than one project. Each run keeps its canonical project path and source identity.
|
|
49
|
+
|
|
50
|
+
The host process performs only bounded protocol handling, short SQLite transactions, timers, queue scheduling, and process supervision. It does not import or execute workflow definitions.
|
|
51
|
+
|
|
52
|
+
A worker loads one workflow source and executes one run generation. It cannot receive a writable `WorkflowRunStore`. It proposes changes to the host over a private child channel. This is an architectural guard against accidental writes. It is not a security sandbox against code running as the same operating-system user.
|
|
53
|
+
|
|
54
|
+
## Host lifecycle
|
|
55
|
+
|
|
56
|
+
The package CLI owns host lifecycle commands:
|
|
57
|
+
|
|
58
|
+
```text
|
|
59
|
+
pi-workflows host start
|
|
60
|
+
pi-workflows host status
|
|
61
|
+
pi-workflows host stop
|
|
62
|
+
pi-workflows host run
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`run` stays attached for direct operation and tests. `start` starts the package process on demand and waits for a ready handshake. It does not install systemd, launchd, or another persistent service.
|
|
66
|
+
|
|
67
|
+
The host uses one global lock and one host epoch. Socket creation and the SQLite host claim must agree before the host accepts commands. A second live host refuses to start. After the old host lease expires, a new host increases the epoch before it handles work. Messages from an older epoch are rejected.
|
|
68
|
+
|
|
69
|
+
The host stays alive while it has a connected client, an active worker, a scheduled wake, a pending controller, or unsettled work. An idle host may exit after a documented idle period. A later client can start it again.
|
|
70
|
+
|
|
71
|
+
## Claim rules
|
|
72
|
+
|
|
73
|
+
A run claim contains:
|
|
74
|
+
|
|
75
|
+
- owner type and owner ID;
|
|
76
|
+
- token hash;
|
|
77
|
+
- generation;
|
|
78
|
+
- acquisition time;
|
|
79
|
+
- heartbeat time;
|
|
80
|
+
- expiry time.
|
|
81
|
+
|
|
82
|
+
A protected state write uses one SQLite transaction:
|
|
83
|
+
|
|
84
|
+
1. Read the expected resource revision and lease.
|
|
85
|
+
2. Compare owner type, owner ID, token hash, and generation.
|
|
86
|
+
3. Require a future expiry time.
|
|
87
|
+
4. Renew heartbeat and expiry for that exact claim.
|
|
88
|
+
5. Apply the domain change.
|
|
89
|
+
6. Add the immutable event and viewer delta.
|
|
90
|
+
7. Increase the resource revision.
|
|
91
|
+
8. Commit.
|
|
92
|
+
|
|
93
|
+
The transaction fails without changes when any check fails.
|
|
94
|
+
|
|
95
|
+
An expired claim cannot renew itself, even when the owner ID and token hash still match. Recovery first takes a new claim and increases the generation.
|
|
96
|
+
|
|
97
|
+
The host also renews active claims from a timer. The timer is a backup for a run with no state writes. Normal write correctness does not depend on the timer.
|
|
98
|
+
|
|
99
|
+
Claim rejection uses `ClaimLostError` with one internal reason:
|
|
100
|
+
|
|
101
|
+
- `missingAuthority`
|
|
102
|
+
- `expired`
|
|
103
|
+
- `ownerChanged`
|
|
104
|
+
- `tokenChanged`
|
|
105
|
+
- `generationChanged`
|
|
106
|
+
|
|
107
|
+
Logs may show the run ID, generation, and reason. They must not show a raw token or token hash.
|
|
108
|
+
|
|
109
|
+
## Run lifecycle
|
|
110
|
+
|
|
111
|
+
The run and queue projections follow these states:
|
|
112
|
+
|
|
113
|
+
| Run state | Queue state | Claim | Worker | Meaning |
|
|
114
|
+
| ----------- | ----------- | ----- | -------- | ------------------------------------------------------- |
|
|
115
|
+
| `queued` | `queued` | none | none | Ready for host scheduling. |
|
|
116
|
+
| `running` | `starting` | host | starting | A worker launch is being recorded. |
|
|
117
|
+
| `running` | `running` | host | live | A worker is executing one node. |
|
|
118
|
+
| `running` | `parked` | none | none | Execution stopped at a durable boundary and can resume. |
|
|
119
|
+
| `waiting` | `parked` | none | none | A checkpoint or interactive request needs input. |
|
|
120
|
+
| `completed` | `done` | none | none | The run finished successfully. |
|
|
121
|
+
| `failed` | `failed` | none | none | The run failed with a durable error. |
|
|
122
|
+
| `timed_out` | `failed` | none | none | The run exceeded a declared timeout. |
|
|
123
|
+
| `cancelled` | `cancelled` | none | none | Cancellation completed. |
|
|
124
|
+
|
|
125
|
+
A lifecycle transaction updates the run, queue, attempt, decision, lease, event, and viewer facts that belong to one transition. The database must not commit a failed event while the run remains running, or a terminal queue row while the run remains nonterminal.
|
|
126
|
+
|
|
127
|
+
Waiting and paused work does not keep a worker or a live claim. Resume takes a new claim generation and starts a new worker from the last durable boundary.
|
|
128
|
+
|
|
129
|
+
## Worker lifecycle
|
|
130
|
+
|
|
131
|
+
A worker launch envelope contains:
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
{
|
|
135
|
+
"schema": "pi-workflows.worker-launch.v1",
|
|
136
|
+
"runId": "run-id",
|
|
137
|
+
"generation": 2,
|
|
138
|
+
"workerEpoch": "opaque-id",
|
|
139
|
+
"projectPath": "/canonical/project/path",
|
|
140
|
+
"workflowSource": {
|
|
141
|
+
"root": {
|
|
142
|
+
"kind": "file",
|
|
143
|
+
"path": "/canonical/project/path/.pi/workflows/example.workflow.ts",
|
|
144
|
+
"hash": "sha256-digest"
|
|
145
|
+
},
|
|
146
|
+
"mounted": []
|
|
147
|
+
},
|
|
148
|
+
"definitionDigest": "sha256:digest",
|
|
149
|
+
"inputHash": "sha256:digest",
|
|
150
|
+
"protocolVersion": 1
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Before it loads workflow modules, the worker verifies the root identity and every saved mounted file hash or built-in revision. After loading, it also checks the complete mounted-source map against the saved map. A mismatch parks the run with `workflowSourceChanged`. The normal scheduler does not claim that run again. The operator can restore the recorded source and explicitly resume the run, or cancel it. Changed included code does not execute.
|
|
155
|
+
|
|
156
|
+
The host records a worker epoch before spawn. The child must return a ready message before the startup deadline. Every later child message includes the run ID, generation, and worker epoch.
|
|
157
|
+
|
|
158
|
+
The host records one terminal worker outcome:
|
|
159
|
+
|
|
160
|
+
- `exited`
|
|
161
|
+
- `cancelled`
|
|
162
|
+
- `timedOut`
|
|
163
|
+
- `crashed`
|
|
164
|
+
- `claimLost`
|
|
165
|
+
- `orphaned`
|
|
166
|
+
|
|
167
|
+
A worker exit is not automatically a run failure. The host decides from the last committed attempt and effect state whether it can resume, must park, or must fail.
|
|
168
|
+
|
|
169
|
+
## Process supervision
|
|
170
|
+
|
|
171
|
+
Each worker starts in its own process group. A headless Pi child starts in another process group so normal worker completion can stop all Pi tool descendants without signaling the worker itself. The worker registers that direct child with the host before it sends a prompt and unregisters it only after group shutdown. The host owns the one process registry and reaps a registered child if its worker exits first.
|
|
172
|
+
|
|
173
|
+
The host enforces:
|
|
174
|
+
|
|
175
|
+
- a startup handshake deadline;
|
|
176
|
+
- node deadlines already declared by the workflow engine;
|
|
177
|
+
- bounded protocol messages;
|
|
178
|
+
- bounded captured stdout and stderr;
|
|
179
|
+
- cancellation with `SIGTERM` and bounded `SIGKILL` escalation;
|
|
180
|
+
- process-group cleanup;
|
|
181
|
+
- orphan checks after host restart;
|
|
182
|
+
- portable memory or process limits where Node and the operating system support them.
|
|
183
|
+
|
|
184
|
+
The child protocol must apply backpressure. A child that exceeds message or output limits fails its worker epoch with a clear infrastructure reason. The complete durable workflow result stays in SQLite within the existing value limits.
|
|
185
|
+
|
|
186
|
+
The process registry includes a process start identity, not only a PID. The host accepts a worker registration only when the PID is a direct child of that active worker. A reused PID cannot let a new host kill an unrelated process.
|
|
187
|
+
|
|
188
|
+
## Local client protocol
|
|
189
|
+
|
|
190
|
+
Clients connect through a user-only local socket. Unix socket mode is `0600`. Other platforms use their equivalent local transport and access control.
|
|
191
|
+
|
|
192
|
+
Messages use newline-delimited canonical JSON. One message is at most 1 MiB, matching the existing durable event limit. The receiver closes only the offending connection when framing or validation fails.
|
|
193
|
+
|
|
194
|
+
Every request uses this envelope:
|
|
195
|
+
|
|
196
|
+
```json
|
|
197
|
+
{
|
|
198
|
+
"schema": "pi-workflows.host-request.v1",
|
|
199
|
+
"requestId": "opaque-id",
|
|
200
|
+
"clientId": "opaque-id",
|
|
201
|
+
"operation": "run.cancel",
|
|
202
|
+
"runId": "run-id",
|
|
203
|
+
"expectedRevision": 12,
|
|
204
|
+
"idempotencyKey": "stable-key",
|
|
205
|
+
"payload": {}
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
A response uses:
|
|
210
|
+
|
|
211
|
+
```json
|
|
212
|
+
{
|
|
213
|
+
"schema": "pi-workflows.host-response.v1",
|
|
214
|
+
"requestId": "opaque-id",
|
|
215
|
+
"outcome": "accepted",
|
|
216
|
+
"revision": 13,
|
|
217
|
+
"receipt": {}
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Valid outcomes are:
|
|
222
|
+
|
|
223
|
+
- `accepted`
|
|
224
|
+
- `adopted`
|
|
225
|
+
- `rejected`
|
|
226
|
+
- `conflict`
|
|
227
|
+
- `notFound`
|
|
228
|
+
- `claimLost`
|
|
229
|
+
- `unavailable`
|
|
230
|
+
|
|
231
|
+
The host commits a command receipt before it acknowledges success. Repeating the same request ID and payload returns the stored receipt. Reusing an ID with another payload returns a conflict.
|
|
232
|
+
|
|
233
|
+
The first command set is:
|
|
234
|
+
|
|
235
|
+
- `run.start`
|
|
236
|
+
- `run.pause`
|
|
237
|
+
- `run.resume`
|
|
238
|
+
- `run.cancel`
|
|
239
|
+
- `run.status`
|
|
240
|
+
- `run.list`
|
|
241
|
+
- `checkpoint.answer`
|
|
242
|
+
- `decision.answer`
|
|
243
|
+
- `interaction.submit`
|
|
244
|
+
- `interaction.update`
|
|
245
|
+
- `notification.claim`
|
|
246
|
+
- `notification.deliver`
|
|
247
|
+
- `turn.claim`
|
|
248
|
+
- `turn.resolve`
|
|
249
|
+
- `controller.list`
|
|
250
|
+
- `controller.get`
|
|
251
|
+
- `controller.apply`
|
|
252
|
+
- `controller.reconcile`
|
|
253
|
+
- `controller.delete`
|
|
254
|
+
- `host.status`
|
|
255
|
+
- `host.stop`
|
|
256
|
+
|
|
257
|
+
Read operations may use the existing read-only store directly in viewers. Mutating Pi and CLI paths use the host.
|
|
258
|
+
|
|
259
|
+
## Worker protocol
|
|
260
|
+
|
|
261
|
+
The private worker channel accepts these message kinds:
|
|
262
|
+
|
|
263
|
+
- `worker.ready`
|
|
264
|
+
- `node.started`
|
|
265
|
+
- `node.update`
|
|
266
|
+
- `node.finished`
|
|
267
|
+
- `node.failed`
|
|
268
|
+
- `run.parked`
|
|
269
|
+
- `run.finished`
|
|
270
|
+
- `interaction.requested`
|
|
271
|
+
- `interaction.accepted`
|
|
272
|
+
- `interaction.rejected`
|
|
273
|
+
- `notification.requested`
|
|
274
|
+
- `presentation.requested`
|
|
275
|
+
- `effect.reserve`
|
|
276
|
+
- `effect.settle`
|
|
277
|
+
- `worker.progress`
|
|
278
|
+
- `worker.exiting`
|
|
279
|
+
|
|
280
|
+
Every worker message includes the worker launch schema, run ID, generation, worker epoch, attempt ID when applicable, expected revision, and a stable message ID. Headless workers use `process.register` and `process.unregister` operations under `worker.progress` to attach their Pi child group to host supervision. Registration requires the live run claim. Unregistration remains valid after a terminal state releases that claim so cleanup can finish.
|
|
281
|
+
|
|
282
|
+
The host checks the generation and epoch before it reads the payload. A stale worker gets one claim-loss response and must exit. The host stores receipts for accepted state-changing messages so a retry receives the same answer.
|
|
283
|
+
|
|
284
|
+
## Durable protocol records
|
|
285
|
+
|
|
286
|
+
Reuse current rows when they already own a fact:
|
|
287
|
+
|
|
288
|
+
- `runs`, `run_queue`, `leases`, and `events` own run lifecycle and claims.
|
|
289
|
+
- `node_attempts` owns node execution state and resolved wall-clock deadlines.
|
|
290
|
+
- `human_decisions` and resolution tables own checkpoints.
|
|
291
|
+
- `effects` and `effect_attempts` own side effects and ambiguous outcomes.
|
|
292
|
+
- `notifications` and `turn_intents` own passive and terminal Pi messages.
|
|
293
|
+
- `run_bindings` owns origin session and execution mode.
|
|
294
|
+
|
|
295
|
+
Add only these records if implementation proves the current rows cannot hold the contract:
|
|
296
|
+
|
|
297
|
+
### Host commands
|
|
298
|
+
|
|
299
|
+
`host_commands` stores request ID, client ID, operation, idempotency key, request fingerprint, run ID, accepted revision, outcome, receipt or error hash, and timestamps. The unique request fingerprint prevents one request ID from naming two commands.
|
|
300
|
+
|
|
301
|
+
### Interactive requests
|
|
302
|
+
|
|
303
|
+
`interactive_requests` stores request ID, run ID, attempt ID, target session ID, kind, contract hash, pending or settled status, accepted submission ID, and timestamps. One attempt has at most one request. The linked node attempt stores its resolved wall-clock deadline.
|
|
304
|
+
|
|
305
|
+
`interactive_submissions` stores request ID, submission ID, idempotency key, payload hash, validating, accepted, or rejected outcome, receipt hash, and submission time. Repeated keys return the same receipt.
|
|
306
|
+
|
|
307
|
+
### Worker epochs
|
|
308
|
+
|
|
309
|
+
`run_workers` stores run ID, generation, worker epoch, launch envelope hash, process identity, status, start time, ready time, finish time, exit code, signal, and bounded diagnostic hash. One run and generation can have several sequential worker epochs, but only one may be active.
|
|
310
|
+
|
|
311
|
+
These tables remain part of `pi-workflows-state` schema version 1. The DDL digest changes in place under the alpha policy.
|
|
312
|
+
|
|
313
|
+
## Interactive Pi execution
|
|
314
|
+
|
|
315
|
+
Agent and assistant-message steps for an interactive run execute in the origin Pi session.
|
|
316
|
+
|
|
317
|
+
The worker commits the node's resolved wall-clock deadline before it proposes `interaction.requested`. The host commits the request, changes the node attempt to waiting, parks the queue row, releases the claim, and acknowledges the worker. The worker then exits. The host continues to enforce the durable deadline while no worker exists. If the deadline passes, one control claim atomically closes the stale request and schedules a supervised timeout-resume child. The child preserves the same attempt and deadline, records `timed_out`, and follows any `$result.outcome` edge. A run with no timeout recovery edge becomes terminal and releases its session reservation. Restart recovery starts this timeout path before it schedules other work.
|
|
318
|
+
|
|
319
|
+
The extension finds pending requests during `session_start`, after `agent_settled`, and once per second while the session is open. It claims one request presentation, sends the step message through documented Pi APIs, and exposes the normal `workflow` tool contract.
|
|
320
|
+
|
|
321
|
+
A tool update or submission goes to the host. It includes the exact request, node, attempt, expected revision, and tool-call idempotency key. The host first checks this transport contract and records a provisional `validating` submission. It then schedules a supervised workflow child. Only that child loads workflow code and runs the node's `validate` function. The child reports `interaction.accepted` or `interaction.rejected` to the host. The host settles the request only after acceptance. A rejected payload leaves the same request pending and returns the stored actionable error to the model. If the child stops before it reports a result, the host rejects the provisional submission and leaves the request ready for a corrected retry.
|
|
322
|
+
|
|
323
|
+
An ordinary checkpoint accepts the model-facing `answer` action and starts a continuation run. A protected human decision never accepts that tool action. The extension displays the decision without starting a model turn, and a person answers it with `/workflow answer` through `decision.answer`. When a protected decision reaches its saved `onTimeout` deadline, the host takes a control claim on the waiting parent, atomically records the validated default, closes the pending interaction, releases the parent claim, and reserves the continuation. A human answer cannot win after that deadline.
|
|
324
|
+
|
|
325
|
+
The session keeps normal Pi entries for prompts, tools, and replies. Pi Workflows stores the public session entry ID used for presentation adoption. It does not edit the Pi session file or schema.
|
|
326
|
+
|
|
327
|
+
One session presents one workflow interaction at a time. Other requests remain ordered by creation time. A restart or reload can present an unresolved request again, but exact session-entry adoption prevents a second visible message when the first presentation was already recorded.
|
|
328
|
+
|
|
329
|
+
Notify nodes enqueue passive messages in the existing `notifications` outbox. The extension claims a message through the host, adopts an existing session entry after a crash, and marks delivery through the host. A completed run with a root `presentationPrompt` creates an ineligible `turn_intent` before the terminal commit. The same terminal transaction makes that intent eligible. The extension claims it, starts one normal Pi turn, and records the public session entry ID. No completion turn starts before the completed state is durable.
|
|
330
|
+
|
|
331
|
+
## Detached execution
|
|
332
|
+
|
|
333
|
+
A run with headless execution mode uses the existing `pi --mode rpc` integration for agent steps. The Pi child uses a separate process group registered with the host. The worker stops that group during normal completion. Cancellation gives the worker a bounded cleanup interval, and the host reaps the registered group if the worker exits first.
|
|
334
|
+
|
|
335
|
+
The headless child receives only the workflow step prompt, configured model arguments, and the bridge extension. Its submission uses the same step and attempt contract as origin-session work. A headless run cannot use a visible assistant-message step because it has no origin Pi session.
|
|
336
|
+
|
|
337
|
+
The run binding records `interactive` or `headless` execution mode. Viewers show that mode without exposing provider credentials.
|
|
338
|
+
|
|
339
|
+
## Pause and cancellation
|
|
340
|
+
|
|
341
|
+
Pause atomically commits `paused = 1`, parks the queue, releases the exact claim, and stores the command receipt. The fenced worker process group then stops. An uncommitted pure node can run again after resume. Resume takes a new generation and starts another worker from the last durable boundary.
|
|
342
|
+
|
|
343
|
+
Cancellation against a live worker atomically commits terminal cancellation, cancels pending attempt and interaction state, settles effect recovery state, releases the exact claim, and stores the command receipt. A pending effect becomes cancelled. An applying effect becomes ambiguous because the host cannot prove its external outcome. The host then stops the fenced worker process group. A host crash after the receipt cannot resume the cancelled run or retry the ambiguous effect. If the child does not stop by the deadline, the host kills its process group.
|
|
344
|
+
|
|
345
|
+
Cancellation against an expired running row first takes a new control claim. The claim operation must prove that the old lease is absent or expired. The new owner then cancels the active attempt, effect recovery state, and pending interaction or human decision in one lifecycle transaction.
|
|
346
|
+
|
|
347
|
+
A client cannot force-cancel a live claim through the stale recovery path.
|
|
348
|
+
|
|
349
|
+
## Recovery
|
|
350
|
+
|
|
351
|
+
At startup the host:
|
|
352
|
+
|
|
353
|
+
1. Takes the global host epoch.
|
|
354
|
+
2. Reaps worker records that match an exact stale process identity.
|
|
355
|
+
3. Finds expired running runs and active attempts.
|
|
356
|
+
4. Reads managed effect state before deciding whether work can repeat.
|
|
357
|
+
5. Parks uncertain effects for manual review.
|
|
358
|
+
6. Makes pure and fully settled work claimable.
|
|
359
|
+
7. Restores pending interactive requests and scheduled controller work.
|
|
360
|
+
8. Starts supervised timeout recovery for pending interactive requests whose durable node deadlines expired.
|
|
361
|
+
9. Resumes any remaining provisional `validating` submission in a new supervised child.
|
|
362
|
+
10. Starts no model turn until a matching Pi session connects or headless mode is declared.
|
|
363
|
+
|
|
364
|
+
Recovery resumes from the last committed boundary. An uncommitted compute node may run again because compute is pure. An action with a stored effect receipt adopts that receipt. An effect in `ambiguous` state requires explicit recovery.
|
|
365
|
+
|
|
366
|
+
Claim loss is a handoff, not a run failure. The old owner writes no terminal event after claim loss.
|
|
367
|
+
|
|
368
|
+
## Effects and retry safety
|
|
369
|
+
|
|
370
|
+
Compute nodes must not perform external side effects. They can repeat after a worker crash.
|
|
371
|
+
|
|
372
|
+
Side-effecting action and shell behavior must have one of these contracts:
|
|
373
|
+
|
|
374
|
+
- a managed effect with an external idempotency key;
|
|
375
|
+
- a managed effect with a read-back check that proves whether it applied;
|
|
376
|
+
- an explicit non-resumable result that becomes `ambiguous` after an uncertain crash.
|
|
377
|
+
|
|
378
|
+
The host reserves an effect before execution. The effect key includes the source resource, effect type, and author-provided idempotency key. The request fingerprint prevents key reuse with another payload.
|
|
379
|
+
|
|
380
|
+
An applied, rejected, or cancelled effect is terminal. An ambiguous effect is also terminal for automatic retry. An operator may use a separate reviewed recovery action after inspecting the external system.
|
|
381
|
+
|
|
382
|
+
## Controllers
|
|
383
|
+
|
|
384
|
+
The global host also reconciles controllers. Controllers keep their existing resource claims, queue, effects, and child workflow request keys.
|
|
385
|
+
|
|
386
|
+
A controller child run enters the same global run queue and worker process model. It does not need an origin Pi session unless its workflow declares an interactive step. A headless child uses the declared provider path. A child that needs an origin session parks with a clear unsupported-input result unless the controller supplied an approved session binding.
|
|
387
|
+
|
|
388
|
+
Controller reconcile code runs in a supervised controller worker, not in the host event loop. Controller initialization also runs in a source resolver child. Before `controller.apply` commits, the host checks that the resolved source still matches controller discovery rules and the exact source digest.
|
|
389
|
+
|
|
390
|
+
## Failure classification
|
|
391
|
+
|
|
392
|
+
Use separate states and messages for these failures:
|
|
393
|
+
|
|
394
|
+
- `claimLost`: another generation owns the run, or the claim expired.
|
|
395
|
+
- `workerCrashed`: the child exited without a terminal protocol message.
|
|
396
|
+
- `workerTimedOut`: the child exceeded a declared deadline.
|
|
397
|
+
- `hostUnavailable`: the client cannot reach or start the host.
|
|
398
|
+
- `sourceChanged`: the workflow source does not match the saved identity.
|
|
399
|
+
- `effectAmbiguous`: an external action may have applied without a receipt.
|
|
400
|
+
- `nodeFailed`: workflow code returned a normal failure.
|
|
401
|
+
- `protocolRejected`: a message failed schema, revision, attempt, or idempotency checks.
|
|
402
|
+
|
|
403
|
+
A failure in one class must not be reported as another. In particular, claim loss does not create a failed run event.
|
|
404
|
+
|
|
405
|
+
## Status and privacy
|
|
406
|
+
|
|
407
|
+
`pi-workflows host status` reports:
|
|
408
|
+
|
|
409
|
+
- host state and epoch;
|
|
410
|
+
- socket availability;
|
|
411
|
+
- active worker count;
|
|
412
|
+
- queued, running, parked, and waiting counts;
|
|
413
|
+
- expired claim count;
|
|
414
|
+
- pending interaction count;
|
|
415
|
+
- ambiguous effect count.
|
|
416
|
+
|
|
417
|
+
It does not print actor IDs, session IDs, project paths, prompts, outputs, payloads, claim tokens, environment variables, or credentials.
|
|
418
|
+
|
|
419
|
+
Logs use bounded safe errors. Child stdout and stderr may contain private content and stay in the user-only workflow state directory. Public issue and pull-request text must use generic fixtures and no operator-specific identifiers.
|
|
420
|
+
|
|
421
|
+
## Alpha state policy
|
|
422
|
+
|
|
423
|
+
This feature changes the current schema in place while the project is in alpha.
|
|
424
|
+
|
|
425
|
+
Keep `pi-workflows-state` and schema version 1. Change the DDL digest and current contracts directly. Add no compatibility reader, migration shim, dual read, dual write, alias, feature flag, or parallel state root.
|
|
426
|
+
|
|
427
|
+
When the installed state has the old digest, fail before mutation with the standard backup and reset instruction. Leave the old database untouched.
|
|
428
|
+
|
|
429
|
+
## Pi API impact
|
|
430
|
+
|
|
431
|
+
- **Session state:** Pi appends normal messages and tool results. Pi Workflows does not edit session files.
|
|
432
|
+
- **Other persistent data:** The workflow SQLite shape changes in place and older alpha state requires reset.
|
|
433
|
+
- **Pi internals:** None.
|
|
434
|
+
- **Public API:** The extension uses documented command registration, tool registration, session lifecycle events, message sending, widgets, status, and session IDs.
|
|
435
|
+
|
|
436
|
+
## Conformance
|
|
437
|
+
|
|
438
|
+
The implementation conforms when:
|
|
439
|
+
|
|
440
|
+
- every protected write checks and renews one live claim atomically;
|
|
441
|
+
- an expired or replaced owner cannot write;
|
|
442
|
+
- a blocked worker cannot stop host renewal;
|
|
443
|
+
- Pi can restart while work computes or waits;
|
|
444
|
+
- the host can restart and recover from committed state;
|
|
445
|
+
- run, queue, attempt, decision, lease, event, and viewer projections remain consistent after injected crashes;
|
|
446
|
+
- an expired running row can be resumed or cancelled safely;
|
|
447
|
+
- duplicate commands and submissions return stored receipts;
|
|
448
|
+
- an interactive request appears once in the origin session and survives reload;
|
|
449
|
+
- effects are deduplicated or marked ambiguous;
|
|
450
|
+
- the extension and host run no workflow or controller code in their own event loops;
|
|
451
|
+
- the production package contains no embedded execution fallback;
|
|
452
|
+
- real Pi end-to-end tests, repository checks, reviewer checks, and CI pass.
|
package/docs/development.md
CHANGED
|
@@ -3,15 +3,18 @@
|
|
|
3
3
|
This document covers the standards for working on pi-workflows itself. For
|
|
4
4
|
authoring workflows, see [workflows.md](workflows.md).
|
|
5
5
|
|
|
6
|
+
The viewer follows the [incremental and virtualized viewer plan](plans/2026-08-28-piw-incremental-viewer-plan.md). Durable writers create revisioned target patches. Rust reads small run-list rows, bounded replay pages, and one shared watched-run projection. Rust and TypeScript share graph fixtures and the same retained scene contract.
|
|
7
|
+
|
|
6
8
|
## Layout and boundaries
|
|
7
9
|
|
|
8
10
|
```
|
|
9
11
|
src/workflows/ finite graph engine: definitions, execution, SQLite stores, loader
|
|
10
12
|
src/builtins/ default workflows shipped at lowest discovery precedence
|
|
11
13
|
src/controllers/ durable resources, queue, reconciliation, effects, child runs
|
|
12
|
-
src/
|
|
14
|
+
src/host/ global host, local protocols, resolvers, and worker supervision
|
|
15
|
+
src/extension/ thin Pi client: commands, workflow tool, and origin-session bridge
|
|
13
16
|
src/viewer/ standalone read-only views over runs and controller resources
|
|
14
|
-
tui/ Rust piw viewer and
|
|
17
|
+
tui/ Rust piw viewer, bounded SQLite projection, and replay server
|
|
15
18
|
```
|
|
16
19
|
|
|
17
20
|
The dependency direction is enforced by `slophammer.yml`. `src/workflows`
|
|
@@ -20,10 +23,12 @@ progress validation, estimation, and text formatting stay in this layer so the
|
|
|
20
23
|
engine, extension, hosts, and viewers share one contract. `src/builtins` contains
|
|
21
24
|
package-owned definitions and imports only the public workflow engine.
|
|
22
25
|
`src/controllers` may import the public workflow engine for child-run
|
|
23
|
-
scheduling. `src/
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
scheduling. `src/host` may import controller stores and workflow stores, but the
|
|
27
|
+
host event loop never loads workflow or controller definitions. Resolver and
|
|
28
|
+
worker child entry points load those definitions. The extension uses static
|
|
29
|
+
built-in metadata and never imports the workflow engine. The extension and
|
|
30
|
+
viewer never import each other. The viewer reads SQLite state in read-only mode,
|
|
31
|
+
so it works from any process.
|
|
27
32
|
|
|
28
33
|
Within `src/render`, `graph.ts` computes a pure layered layout (ported from
|
|
29
34
|
the acpx replay viewer: labelled switch expansion, DFS back-edge detection,
|
|
@@ -31,12 +36,7 @@ longest-path layering, barycenter ordering, virtual pass-through cells for
|
|
|
31
36
|
long edges), `canvas.ts` is a character grid that merges box-drawing
|
|
32
37
|
characters by connectivity, and `graph-render.ts` turns a SQLite run state plus a
|
|
33
38
|
replay position into the drawn graph in one of two node styles: `box`
|
|
34
|
-
(bordered nodes
|
|
35
|
-
(single-line nodes). The widget windows the boxed graph around the active
|
|
36
|
-
node to stay inside pi's 10-line widget cap; `shift+↑`/`shift+↓` shortcuts
|
|
37
|
-
(registered through pi's `registerShortcut`) scroll that window manually, and
|
|
38
|
-
the scroll resets to follow mode when the run records a new step. `render.ts`
|
|
39
|
-
in `src/viewer`
|
|
39
|
+
(bordered nodes) or `line` (single-line nodes). `render.ts` in `src/viewer`
|
|
40
40
|
composes the full detail view (header, graph, step timeline, step inspector)
|
|
41
41
|
and stays pure so tests can assert on rendered lines.
|
|
42
42
|
|
|
@@ -63,18 +63,16 @@ workflow shapes at every replay position; if a rendering change breaks a
|
|
|
63
63
|
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
|
-
Inside the engine, the
|
|
67
|
-
The
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
the extension side of that seam.
|
|
66
|
+
Inside the engine, the model-facing seam is the `AgentStepExecutor` interface.
|
|
67
|
+
The production run worker uses an interaction executor for origin-session work
|
|
68
|
+
and `RpcStepExecutor` for headless controller work. The worker receives a
|
|
69
|
+
`HostBackedWorkflowStore`; it cannot open a writable production store.
|
|
71
70
|
|
|
72
|
-
|
|
73
|
-
Pi
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
the `session_segments` capture status and never fail workflow execution.
|
|
71
|
+
The Pi extension uses documented session lifecycle, message, tool, and session
|
|
72
|
+
manager APIs. It writes no Pi session file. It saves host mutations through the
|
|
73
|
+
local protocol and uses the public session branch only to adopt the exact
|
|
74
|
+
visible entry for a durable interaction request. Tests can still use scripted
|
|
75
|
+
executors through the Pi-agnostic engine API.
|
|
78
76
|
|
|
79
77
|
`src/viewer/session-reducer.ts` and `tui/src/session.rs` implement the same
|
|
80
78
|
sequence-ordered fold. Shared fixtures in `fixtures/session-events/` pin their
|
|
@@ -104,6 +102,23 @@ npx slophammer-ts@latest dry .
|
|
|
104
102
|
npx slophammer-ts@latest check . --only ts.dependency-boundaries-required
|
|
105
103
|
```
|
|
106
104
|
|
|
105
|
+
## Viewer performance checks
|
|
106
|
+
|
|
107
|
+
Create a synthetic growing database and run the release benchmark:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
mkdir -m 700 /tmp/piw-viewer-benchmark
|
|
111
|
+
npx tsx scripts/generate-viewer-benchmark.ts \
|
|
112
|
+
/tmp/piw-viewer-benchmark/state.sqlite 44 405 211 1105 1200
|
|
113
|
+
cargo run --release --manifest-path tui/Cargo.toml \
|
|
114
|
+
--example refresh_benchmark -- \
|
|
115
|
+
/tmp/piw-viewer-benchmark/state.sqlite 1000
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The generator prints only row counts and database size. The benchmark prints run count, query and load counts, payload rows, raw tick times, and peak RSS. It does not print run IDs or payload text.
|
|
119
|
+
|
|
120
|
+
The deterministic gate is zero payload reads after the first bounded selected window during unchanged idle checks. The measured gates are p99 main-thread checks below 16 ms and peak RSS below 185 MB on a database close to the registered 97 MB fixture. Run the benchmark several times and report the median, range, p99, maximum, and peak memory. Do not select extra complexity from one best run.
|
|
121
|
+
|
|
107
122
|
## End-to-end tests
|
|
108
123
|
|
|
109
124
|
```bash
|
|
@@ -120,10 +135,12 @@ the real pi CLI from `devDependencies` in RPC mode with:
|
|
|
120
135
|
- `HOME` pointed at a temporary home containing the canonical workflow database,
|
|
121
136
|
- the extension loaded from source with `-e src/extension/index.ts`.
|
|
122
137
|
|
|
123
|
-
It drives `/workflow` over the RPC protocol and
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
138
|
+
It drives `/workflow` over the RPC protocol and checks the global host,
|
|
139
|
+
durable interaction request, origin-session presentation, accepted submission,
|
|
140
|
+
worker recovery, final SQLite state, and viewer output. It also restarts the
|
|
141
|
+
real Pi process while one request is pending and proves that the same request is
|
|
142
|
+
not inserted twice. Nothing outside the temp directories is touched, and no
|
|
143
|
+
real model is called.
|
|
127
144
|
|
|
128
145
|
## Publishing
|
|
129
146
|
|
|
@@ -155,9 +172,8 @@ before publishing.
|
|
|
155
172
|
## Conventions
|
|
156
173
|
|
|
157
174
|
- Conventional Commits for commit messages and PR titles.
|
|
158
|
-
- Persisted JSON uses camelCase keys and
|
|
159
|
-
[SQLITE_STATE.md](SQLITE_STATE.md).
|
|
160
|
-
the schema version string.
|
|
175
|
+
- Persisted JSON uses camelCase keys and current `schema` identifiers; see
|
|
176
|
+
[SQLITE_STATE.md](SQLITE_STATE.md). During alpha, a breaking shape changes the current contract in place and uses the clear reset failure. Do not add a compatibility path or `v2` only to preserve old alpha state.
|
|
161
177
|
- Every exported API of the engine (`src/workflows/index.ts`) is covered by
|
|
162
178
|
unit tests; new node types or edge semantics need tests in `test/` and a
|
|
163
179
|
section in [workflows.md](workflows.md).
|