@osolmaz/pi-workflows 0.2.0 → 0.4.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 +72 -5
- package/dist/builtins/catalog.d.ts +2 -0
- package/dist/builtins/catalog.js +22 -0
- package/dist/builtins/catalog.js.map +1 -0
- package/dist/builtins/monitor.workflow.d.ts +71 -0
- package/dist/builtins/monitor.workflow.js +250 -0
- package/dist/builtins/monitor.workflow.js.map +1 -0
- package/dist/controllers/conditions.d.ts +6 -0
- package/dist/controllers/conditions.js +68 -0
- package/dist/controllers/conditions.js.map +1 -0
- package/dist/controllers/definition.d.ts +6 -0
- package/dist/controllers/definition.js +45 -0
- package/dist/controllers/definition.js.map +1 -0
- package/dist/controllers/effects.d.ts +14 -0
- package/dist/controllers/effects.js +104 -0
- package/dist/controllers/effects.js.map +1 -0
- package/dist/controllers/errors.d.ts +12 -0
- package/dist/controllers/errors.js +25 -0
- package/dist/controllers/errors.js.map +1 -0
- package/dist/controllers/index.d.ts +13 -0
- package/dist/controllers/index.js +13 -0
- package/dist/controllers/index.js.map +1 -0
- package/dist/controllers/json.d.ts +5 -0
- package/dist/controllers/json.js +57 -0
- package/dist/controllers/json.js.map +1 -0
- package/dist/controllers/loader.d.ts +23 -0
- package/dist/controllers/loader.js +74 -0
- package/dist/controllers/loader.js.map +1 -0
- package/dist/controllers/manager.d.ts +58 -0
- package/dist/controllers/manager.js +399 -0
- package/dist/controllers/manager.js.map +1 -0
- package/dist/controllers/results.d.ts +5 -0
- package/dist/controllers/results.js +32 -0
- package/dist/controllers/results.js.map +1 -0
- package/dist/controllers/sqlite.d.ts +235 -0
- package/dist/controllers/sqlite.js +1087 -0
- package/dist/controllers/sqlite.js.map +1 -0
- package/dist/controllers/store.d.ts +112 -0
- package/dist/controllers/store.js +32 -0
- package/dist/controllers/store.js.map +1 -0
- package/dist/controllers/types.d.ts +159 -0
- package/dist/controllers/types.js +2 -0
- package/dist/controllers/types.js.map +1 -0
- package/dist/controllers/workflow-engine-scheduler.d.ts +25 -0
- package/dist/controllers/workflow-engine-scheduler.js +95 -0
- package/dist/controllers/workflow-engine-scheduler.js.map +1 -0
- package/dist/controllers/workflows.d.ts +27 -0
- package/dist/controllers/workflows.js +109 -0
- package/dist/controllers/workflows.js.map +1 -0
- package/dist/extension/controller-host.d.ts +47 -0
- package/dist/extension/controller-host.js +110 -0
- package/dist/extension/controller-host.js.map +1 -0
- package/dist/extension/executor.d.ts +3 -0
- package/dist/extension/executor.js +12 -2
- package/dist/extension/executor.js.map +1 -1
- package/dist/extension/index.d.ts +7 -0
- package/dist/extension/index.js +1118 -106
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/recorder.d.ts +6 -0
- package/dist/extension/recorder.js +20 -8
- package/dist/extension/recorder.js.map +1 -1
- package/dist/extension/workflow-tool.d.ts +28 -0
- package/dist/extension/workflow-tool.js +33 -0
- package/dist/extension/workflow-tool.js.map +1 -0
- package/dist/host/processes.d.ts +24 -0
- package/dist/host/processes.js +114 -0
- package/dist/host/processes.js.map +1 -0
- package/dist/host/rpc-bridge.d.ts +9 -0
- package/dist/host/rpc-bridge.js +39 -0
- package/dist/host/rpc-bridge.js.map +1 -0
- package/dist/host/rpc-executor.d.ts +38 -0
- package/dist/host/rpc-executor.js +254 -0
- package/dist/host/rpc-executor.js.map +1 -0
- package/dist/host/runner.d.ts +50 -0
- package/dist/host/runner.js +379 -0
- package/dist/host/runner.js.map +1 -0
- package/dist/viewer/cli.d.ts +7 -3
- package/dist/viewer/cli.js +150 -19
- package/dist/viewer/cli.js.map +1 -1
- package/dist/workflows/catalog.d.ts +43 -0
- package/dist/workflows/catalog.js +79 -0
- package/dist/workflows/catalog.js.map +1 -0
- package/dist/workflows/engine.d.ts +37 -2
- package/dist/workflows/engine.js +297 -29
- package/dist/workflows/engine.js.map +1 -1
- package/dist/workflows/errors.d.ts +23 -0
- package/dist/workflows/errors.js +38 -0
- package/dist/workflows/errors.js.map +1 -1
- package/dist/workflows/graph.js +0 -5
- package/dist/workflows/graph.js.map +1 -1
- package/dist/workflows/index.d.ts +2 -2
- package/dist/workflows/index.js +1 -1
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/loader.d.ts +19 -15
- package/dist/workflows/loader.js +63 -19
- package/dist/workflows/loader.js.map +1 -1
- package/dist/workflows/migrate-sources.d.ts +41 -0
- package/dist/workflows/migrate-sources.js +129 -0
- package/dist/workflows/migrate-sources.js.map +1 -0
- package/dist/workflows/schema.js +3 -2
- package/dist/workflows/schema.js.map +1 -1
- package/dist/workflows/store.d.ts +50 -6
- package/dist/workflows/store.js +448 -53
- package/dist/workflows/store.js.map +1 -1
- package/dist/workflows/types.d.ts +27 -3
- package/docs/CONTROLLERS.md +215 -0
- package/docs/development.md +14 -9
- package/docs/plans/2026-08-04-controller-runtime-plan.md +169 -0
- package/docs/plans/2026-08-05-always-on-workflows-plan.md +125 -0
- package/docs/plans/2026-08-10-agent-managed-monitor-workflows-plan.md +184 -0
- package/docs/plans/2026-08-12-coordinated-workflow-timeouts-plan.md +74 -0
- package/docs/plans/2026-08-13-built-in-workflow-catalog-plan.md +97 -0
- package/docs/run-bundles.md +85 -28
- package/docs/workflows.md +161 -17
- package/examples/controllers/pull-request.controller.ts +215 -0
- package/package.json +10 -2
- package/src/builtins/catalog.ts +22 -0
- package/src/builtins/monitor.workflow.ts +302 -0
- package/src/controllers/conditions.ts +110 -0
- package/src/controllers/definition.ts +65 -0
- package/src/controllers/effects.ts +123 -0
- package/src/controllers/errors.ts +27 -0
- package/src/controllers/index.ts +90 -0
- package/src/controllers/json.ts +62 -0
- package/src/controllers/loader.ts +104 -0
- package/src/controllers/manager.ts +533 -0
- package/src/controllers/results.ts +46 -0
- package/src/controllers/sqlite.ts +1545 -0
- package/src/controllers/store.ts +160 -0
- package/src/controllers/types.ts +183 -0
- package/src/controllers/workflow-engine-scheduler.ts +148 -0
- package/src/controllers/workflows.ts +152 -0
- package/src/extension/controller-host.ts +163 -0
- package/src/extension/executor.ts +13 -2
- package/src/extension/index.ts +1325 -130
- package/src/extension/recorder.ts +65 -36
- package/src/extension/workflow-tool.ts +59 -0
- package/src/host/processes.ts +119 -0
- package/src/host/rpc-bridge.ts +44 -0
- package/src/host/rpc-executor.ts +299 -0
- package/src/host/runner.ts +445 -0
- package/src/viewer/cli.ts +167 -21
- package/src/workflows/catalog.ts +135 -0
- package/src/workflows/engine.ts +404 -41
- package/src/workflows/errors.ts +45 -0
- package/src/workflows/graph.ts +0 -5
- package/src/workflows/index.ts +2 -0
- package/src/workflows/loader.ts +76 -22
- package/src/workflows/migrate-sources.ts +167 -0
- package/src/workflows/schema.ts +3 -2
- package/src/workflows/store.ts +557 -48
- package/src/workflows/types.ts +22 -3
package/docs/run-bundles.md
CHANGED
|
@@ -77,9 +77,9 @@ Consequences for readers:
|
|
|
77
77
|
|
|
78
78
|
Large payloads are stored once, content-addressed, under `artifacts/` and
|
|
79
79
|
referenced from the documents that use them. This applies uniformly to every
|
|
80
|
-
**persisted value position
|
|
81
|
-
`steps[*].prompt`, `steps[*].output`, `finalOutput
|
|
82
|
-
values.
|
|
80
|
+
**persisted value position**. These positions include `input`, `outputs.*`,
|
|
81
|
+
`results.*.output`, `steps[*].prompt`, `steps[*].output`, and `finalOutput`.
|
|
82
|
+
Trace event payload values follow the same rule.
|
|
83
83
|
|
|
84
84
|
Encoding rule, applied recursively to a persisted value:
|
|
85
85
|
|
|
@@ -109,9 +109,9 @@ Artifact rules:
|
|
|
109
109
|
- Artifacts are immutable once written and deduplicate by content hash.
|
|
110
110
|
- Readers must tolerate unknown `mediaType` values.
|
|
111
111
|
|
|
112
|
-
|
|
113
|
-
`steps`,
|
|
114
|
-
|
|
112
|
+
The same output can legitimately appear in several places. Externalization
|
|
113
|
+
keeps each copy in `outputs`, `results`, `steps`, or the trace as the same small
|
|
114
|
+
reference.
|
|
115
115
|
|
|
116
116
|
## manifest.json
|
|
117
117
|
|
|
@@ -123,7 +123,11 @@ Identity and pointers, kept in sync with the state on every snapshot:
|
|
|
123
123
|
"runId": "20260729T023912Z-autoimplement-3f2a9c1b",
|
|
124
124
|
"workflowName": "autoimplement",
|
|
125
125
|
"runTitle": "autoimplement: fix the flaky test",
|
|
126
|
-
"
|
|
126
|
+
"workflowSource": {
|
|
127
|
+
"kind": "file",
|
|
128
|
+
"path": "/repo/.pi/workflows/autoimplement.workflow.ts",
|
|
129
|
+
"hash": "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae"
|
|
130
|
+
},
|
|
127
131
|
"startedAt": "2026-07-29T02:39:12.412Z",
|
|
128
132
|
"finishedAt": "2026-07-29T02:41:03.977Z",
|
|
129
133
|
"status": "completed",
|
|
@@ -138,6 +142,11 @@ Identity and pointers, kept in sync with the state on every snapshot:
|
|
|
138
142
|
}
|
|
139
143
|
```
|
|
140
144
|
|
|
145
|
+
`workflowSource` identifies the definition used by the run. User workflow
|
|
146
|
+
files use an absolute path and SHA-256 hash. Package-provided workflows use a
|
|
147
|
+
stable identity such as `{ "kind": "builtin", "id": "monitor", "revision": "1" }`.
|
|
148
|
+
A built-in identity does not contain an installation path.
|
|
149
|
+
|
|
141
150
|
`paths.artifacts` is declared from bundle creation so a live session-event
|
|
142
151
|
patch can safely reference a newly written artifact before the next workflow
|
|
143
152
|
state projection. The directory itself is created only when needed.
|
|
@@ -154,6 +163,30 @@ validators are not serialized. Each node keeps only its metadata (`nodeType`,
|
|
|
154
163
|
and edges are copied verbatim. The snapshot is what lets viewers draw all
|
|
155
164
|
nodes, including ones that have not run yet. It is immutable after run start.
|
|
156
165
|
|
|
166
|
+
## Resume and repair
|
|
167
|
+
|
|
168
|
+
An interrupted run (status `running` with no terminal trace event) can resume
|
|
169
|
+
instead of failing. Resume is a named operation with strict rules:
|
|
170
|
+
|
|
171
|
+
1. The caller must hold the run's queue claim. Only the current claim holder
|
|
172
|
+
may resume or interrupt a bundle, and every bundle write verifies the
|
|
173
|
+
claim token first (write fencing).
|
|
174
|
+
2. A torn trace tail (a crash mid-append) is truncated to the last complete
|
|
175
|
+
line. Trace events the state projection never recorded are dropped, so
|
|
176
|
+
`state.traceSeq` and the trace agree again before any new event.
|
|
177
|
+
3. Completed nodes replay from the projection. The in-flight node reruns with
|
|
178
|
+
a fresh attempt; a `run_resumed` trace event marks the boundary.
|
|
179
|
+
4. `state.workflowSource` pins the workflow source from run start. File
|
|
180
|
+
sources require the same hash. Built-in sources require the same catalog
|
|
181
|
+
id and revision. Resume refuses a mismatch unless forced, and a forced
|
|
182
|
+
resume records the mismatch in the `run_resumed` payload.
|
|
183
|
+
|
|
184
|
+
Continuation runs (answering a checkpoint) are new bundles, not resumed ones.
|
|
185
|
+
They link back through `state.parentRunId`, carry the parent's outputs,
|
|
186
|
+
results, and step records forward, and note `continuedFrom` in their
|
|
187
|
+
`run_started` payload. Bundles stay append-only; a continuation is the only
|
|
188
|
+
way work follows a terminal `waiting` state.
|
|
189
|
+
|
|
157
190
|
## state.json
|
|
158
191
|
|
|
159
192
|
The full run projection (`WorkflowRunState` in
|
|
@@ -166,6 +199,11 @@ The full run projection (`WorkflowRunState` in
|
|
|
166
199
|
"traceSeq": 17,
|
|
167
200
|
"runId": "20260729T023912Z-autoimplement-3f2a9c1b",
|
|
168
201
|
"workflowName": "autoimplement",
|
|
202
|
+
"workflowSource": {
|
|
203
|
+
"kind": "file",
|
|
204
|
+
"path": "/repo/.pi/workflows/autoimplement.workflow.ts",
|
|
205
|
+
"hash": "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae"
|
|
206
|
+
},
|
|
169
207
|
"startedAt": "…",
|
|
170
208
|
"updatedAt": "…",
|
|
171
209
|
"status": "running",
|
|
@@ -176,11 +214,20 @@ The full run projection (`WorkflowRunState` in
|
|
|
176
214
|
}
|
|
177
215
|
```
|
|
178
216
|
|
|
217
|
+
- `workflowSource` is the canonical source identity. Resuming a file requires
|
|
218
|
+
the same hash. Resuming a built-in requires the same catalog revision. A
|
|
219
|
+
mismatch refuses the resume instead of loading another definition.
|
|
179
220
|
- `status` is one of `running`, `waiting`, `completed`, `failed`, `timed_out`,
|
|
180
|
-
or `cancelled`.
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
221
|
+
or `cancelled`. A controller host records an abandoned bundle as `failed`
|
|
222
|
+
with a final `run_interrupted` trace event. Before doing that, recovery checks
|
|
223
|
+
the trace tail and repairs a stale projection when the terminal event was
|
|
224
|
+
already appended. The controller store can then retry a genuinely abandoned
|
|
225
|
+
child attempt without changing this schema. If startup stopped before a
|
|
226
|
+
manifest existed, the scheduler preserves that incomplete directory as a
|
|
227
|
+
hidden sibling before creating the reserved run.
|
|
228
|
+
- While a node is executing, `currentNode` and `currentAttemptId` identify it.
|
|
229
|
+
`currentNodeStartedAt` and `statusDetail` add timing and display context.
|
|
230
|
+
These fields disappear when the node finishes. The executing node's type comes from the definition
|
|
184
231
|
snapshot, not from the state.
|
|
185
232
|
- While a pause request holds the run at a step boundary, `paused` is `true`
|
|
186
233
|
(with matching `run_paused`/`run_resumed` trace events); it disappears when
|
|
@@ -218,10 +265,9 @@ The full run projection (`WorkflowRunState` in
|
|
|
218
265
|
produced by this attempt, from prompt delivery through accepted submission.
|
|
219
266
|
Viewers must use this explicit linkage and never infer it heuristically.
|
|
220
267
|
- Action steps carry an `action` receipt with `actionType`
|
|
221
|
-
(`shell`/`function`)
|
|
222
|
-
`exitCode`, `signal`, and `durationMs`. Shell stdout/stderr live in the
|
|
223
|
-
|
|
224
|
-
large.
|
|
268
|
+
(`shell`/`function`). Shell actions also record `command`, `args`, `cwd`,
|
|
269
|
+
`exitCode`, `signal`, and `durationMs`. Shell stdout/stderr live in the step
|
|
270
|
+
output (the parsed or raw shell result) and are externalized when large.
|
|
225
271
|
- When a run pauses at a checkpoint, `waitingOn` names the checkpoint node.
|
|
226
272
|
Terminal runs carry `finalOutput` on success and `error` on failure.
|
|
227
273
|
|
|
@@ -248,8 +294,8 @@ One event per line, monotonically sequenced per run, schema
|
|
|
248
294
|
ignore unknown event types and unknown payload fields so new ones can be added
|
|
249
295
|
within the same schema version.
|
|
250
296
|
|
|
251
|
-
The trace alone is sufficient to reconstruct the run
|
|
252
|
-
|
|
297
|
+
The trace alone is sufficient to reconstruct the run because terminal node
|
|
298
|
+
events carry outputs and receipts.
|
|
253
299
|
|
|
254
300
|
Event catalog and payload contracts:
|
|
255
301
|
|
|
@@ -268,6 +314,7 @@ Event catalog and payload contracts:
|
|
|
268
314
|
| `run_failed` | run | `status`, `error` |
|
|
269
315
|
| `run_timed_out` | run | `status`, `error` |
|
|
270
316
|
| `run_cancelled` | run | `status`, `error?` |
|
|
317
|
+
| `run_interrupted` | run | `error` |
|
|
271
318
|
|
|
272
319
|
Invariants:
|
|
273
320
|
|
|
@@ -283,6 +330,15 @@ Present when the run executed inside a Pi conversation. The extension records
|
|
|
283
330
|
the conversation into the bundle so replay never depends on Pi's global
|
|
284
331
|
session store.
|
|
285
332
|
|
|
333
|
+
A run that outlives its first session (parked, then resumed by another
|
|
334
|
+
session or the host) gains a second capture under
|
|
335
|
+
`session/segments/<attemptId>/` with the same file layout (`binding.json`,
|
|
336
|
+
`entries.ndjson`, `events.ndjson`, `capture.json`). The first capture stays
|
|
337
|
+
flat at `session/`; only captures from the second bind onward become
|
|
338
|
+
segments, so readers that predate segments keep working on single-session
|
|
339
|
+
bundles. An interrupted run finalizes any segment still `recording` as
|
|
340
|
+
`failed` with the interruption reason.
|
|
341
|
+
|
|
286
342
|
### binding.json
|
|
287
343
|
|
|
288
344
|
Written once when the run binds to the conversation
|
|
@@ -325,22 +381,23 @@ while the run was active, schema `pi-workflows.session-entry.v1`:
|
|
|
325
381
|
- `entry` is the verbatim Pi session entry (Pi's own versioned format),
|
|
326
382
|
including user messages, assistant messages, tool results, model changes,
|
|
327
383
|
and compaction entries. Nothing is normalized or rewritten.
|
|
328
|
-
- Entries include everything that happened in the conversation during the run
|
|
329
|
-
workflow prompts
|
|
384
|
+
- Entries include everything that happened in the conversation during the run.
|
|
385
|
+
This includes workflow prompts and nudges together with user interruptions.
|
|
330
386
|
- `conversation` ranges in step records and `node_finished` events address
|
|
331
387
|
entries by Pi entry id (`entry.id`).
|
|
332
388
|
|
|
333
389
|
### events.ndjson
|
|
334
390
|
|
|
335
|
-
The temporal journal records documented Pi `turn_
|
|
391
|
+
The temporal journal records documented Pi `turn_*` and `message_*` hooks plus
|
|
336
392
|
`tool_execution_*` hooks with schema `pi-workflows.session-event.v1`. Each
|
|
337
|
-
record has a per-file `seq`, timestamp, `nodeId`, `attemptId
|
|
338
|
-
|
|
393
|
+
record has a per-file `seq`, timestamp, `nodeId`, and `attemptId`. Optional
|
|
394
|
+
turn, message, and tool call IDs link related records. A normalized `type` and
|
|
395
|
+
`payload` carry the event data.
|
|
339
396
|
The full contract and event catalog are in
|
|
340
397
|
[session-event-journal.md](session-event-journal.md).
|
|
341
398
|
|
|
342
|
-
Events preserve semantic deltas. Assistant `partial
|
|
343
|
-
terminal `error` snapshots
|
|
399
|
+
Events preserve semantic deltas. Assistant `partial` snapshots are never
|
|
400
|
+
stored, and neither are terminal `message` or `error` snapshots. Tool update records omit Pi's
|
|
344
401
|
cumulative `partialResult`. Final `message_finished` records link to settled
|
|
345
402
|
Pi entries with `entryId`; after that linkage, `entries.ndjson` is the
|
|
346
403
|
verbatim content authority.
|
|
@@ -364,10 +421,10 @@ complete lines, sequence gaps, and terminal torn tails are integrity failures.
|
|
|
364
421
|
}
|
|
365
422
|
```
|
|
366
423
|
|
|
367
|
-
`status`
|
|
368
|
-
`failure` with `failedAt
|
|
369
|
-
readers but does not fail the workflow. Terminal readers verify
|
|
370
|
-
last sequence, schemas
|
|
424
|
+
`status` starts as `recording` and ends as `complete` or `failed`. Failed
|
|
425
|
+
capture adds `failure` with `failedAt` plus a code and message. Capture failure
|
|
426
|
+
is visible to readers but does not fail the workflow. Terminal readers verify
|
|
427
|
+
the counts and last sequence, then check schemas and contiguous event order. Missing temporal files
|
|
371
428
|
in a session-bound bundle are invalid, not an older supported layout.
|
|
372
429
|
|
|
373
430
|
## Versioning
|
package/docs/workflows.md
CHANGED
|
@@ -9,10 +9,21 @@ model sees, and how runs behave at runtime. For the on-disk run format, see
|
|
|
9
9
|
|
|
10
10
|
A workflow is a TypeScript module whose default export is `defineWorkflow(...)`.
|
|
11
11
|
Files are discovered by suffix (`.workflow.ts`, `.workflow.js`, `.workflow.mts`,
|
|
12
|
-
`.workflow.mjs`) from
|
|
12
|
+
`.workflow.mjs`) from these sources, in precedence order:
|
|
13
13
|
|
|
14
14
|
1. `.pi/workflows/` in the project (highest precedence on name collisions)
|
|
15
15
|
2. `~/.pi/agent/workflows/` globally
|
|
16
|
+
3. Workflows built into Pi Workflows
|
|
17
|
+
|
|
18
|
+
Pi Workflows includes a built-in `monitor` workflow. A project or global file
|
|
19
|
+
named `monitor.workflow.ts` replaces it. The package registers each built-in in
|
|
20
|
+
a process-local catalog with a stable reference such as `builtin:monitor` and
|
|
21
|
+
an explicit revision. Built-ins are imported with the engine when a Pi process
|
|
22
|
+
starts. They are not read from the package directory when a run starts or
|
|
23
|
+
resumes. Updating the package on disk cannot mix a new built-in with that
|
|
24
|
+
process's old engine; reload or restart Pi to use the new built-in. A revision
|
|
25
|
+
mismatch refuses resume. Project and global workflow files still reload on
|
|
26
|
+
each run and use their path and SHA-256 hash as their source identity.
|
|
16
27
|
|
|
17
28
|
The workflow's command name is the file stem, so `.pi/workflows/triage.workflow.ts`
|
|
18
29
|
runs as `/workflow triage`. A direct path also works: `/workflow ./somewhere/x.workflow.ts`.
|
|
@@ -44,7 +55,7 @@ Top-level fields:
|
|
|
44
55
|
|
|
45
56
|
| Field | Type | Notes |
|
|
46
57
|
| -------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
47
|
-
| `name` | `string` | Required. Used in run ids and the step contract. `cancel`, `list`, `pause`, and `
|
|
58
|
+
| `name` | `string` | Required. Used in run ids and the step contract. `answer`, `cancel`, `list`, `pause`, `resume`, and `status` are reserved for `/workflow` subcommands. |
|
|
48
59
|
| `title` | `string` or function | Optional run title, resolved once at start from `{ input, workflowName }`. Async resolution is bounded (30s) and cancellable. |
|
|
49
60
|
| `presentationPrompt` | `string` or function | Optional instructions for a normal assistant response after the run. A function receives `{ state, finalOutput, signal }` and may return a prompt or `undefined`. See [Result presentation](#result-presentation). |
|
|
50
61
|
| `startAt` | `string` | Required. Id of the first node. |
|
|
@@ -79,6 +90,38 @@ Long-running compute, action, and checkpoint callbacks should observe
|
|
|
79
90
|
steps). When the node times out or the run is cancelled, the engine stops
|
|
80
91
|
waiting immediately, but only cooperative callbacks stop doing work.
|
|
81
92
|
|
|
93
|
+
## Durable runs, parking, and resume
|
|
94
|
+
|
|
95
|
+
Every interactive `/workflow` run is tracked in the project run queue (see
|
|
96
|
+
[CONTROLLERS.md](CONTROLLERS.md) for the store). The session that starts a run
|
|
97
|
+
claims it and owns it while it executes; every bundle write proves the claim
|
|
98
|
+
first (write fencing).
|
|
99
|
+
|
|
100
|
+
Closing the Pi session mid-run no longer cancels the run. The engine **parks**:
|
|
101
|
+
it stops without a terminal event, releases the claim, and leaves the bundle
|
|
102
|
+
resumable. When a runner is available again (a reopened Pi session or the
|
|
103
|
+
standalone host), the run **resumes** at the node it stopped on. Completed
|
|
104
|
+
nodes replay from the recorded state; only the interrupted node and everything
|
|
105
|
+
downstream rerun. Resume repairs a torn trace tail, drops trace events the
|
|
106
|
+
state projection never recorded, and refuses to continue if the workflow
|
|
107
|
+
source changed since the run started (a forced resume records the mismatch).
|
|
108
|
+
|
|
109
|
+
The standalone host runs without any Pi session:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
pi-workflows host --project /path/to/project
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
The host claims parked runs, resumes them, and reconciles durable controllers.
|
|
116
|
+
Conversation nodes execute in headless `pi --mode rpc` children that load a
|
|
117
|
+
small bridge extension; the model sees the same `workflow` tool contract as an
|
|
118
|
+
in-session run. The host is a foreground process: start it in a terminal and
|
|
119
|
+
stop it with Ctrl-C. A second host for the same project refuses to start, and
|
|
120
|
+
a host that dies has its orphaned children reaped by the next one. While the
|
|
121
|
+
host works, any open Pi session stays current: a per-session watermark over
|
|
122
|
+
the shared run event feed produces catch-up summaries and quiet context
|
|
123
|
+
updates.
|
|
124
|
+
|
|
82
125
|
## Node types
|
|
83
126
|
|
|
84
127
|
### agent
|
|
@@ -91,7 +134,10 @@ agent({
|
|
|
91
134
|
prompt: ({ outputs }) => `Review this: ${JSON.stringify(outputs.implement)}`,
|
|
92
135
|
expectedOutput: `{ "verdict": "clean" | "issues_found" }`,
|
|
93
136
|
validate: (output) => output, // optional; throw to reject the submission
|
|
94
|
-
timeoutMs:
|
|
137
|
+
timeoutMs: ({ input }) =>
|
|
138
|
+
(input as { timeoutMinutes?: number }).timeoutMinutes
|
|
139
|
+
? (input as { timeoutMinutes: number }).timeoutMinutes * 60_000
|
|
140
|
+
: 30 * 60_000, // optional number or context callback; default 15 minutes
|
|
95
141
|
statusDetail: "reviewing", // optional; shown in widget and viewer
|
|
96
142
|
});
|
|
97
143
|
```
|
|
@@ -101,7 +147,15 @@ calls the tool, the output passes through normalization (a JSON string is
|
|
|
101
147
|
parsed tolerantly) and then `validate`. If `validate` throws, the tool call
|
|
102
148
|
returns an error and the model can retry within the same step. If the agent
|
|
103
149
|
ends its turn without submitting, the extension nudges it, twice by default,
|
|
104
|
-
then fails the step.
|
|
150
|
+
then fails the step. If an agent node times out or the workflow is cancelled,
|
|
151
|
+
the extension also aborts its active Pi turn. The model cannot continue to use
|
|
152
|
+
tools after the engine has closed that attempt.
|
|
153
|
+
|
|
154
|
+
`timeoutMs` can be a finite positive number or a function of the normal node
|
|
155
|
+
context. A timeout function can use prepared outputs to select a deadline for
|
|
156
|
+
this run. It has 30 seconds to return a value. Computed timeout functions are
|
|
157
|
+
runtime code, so definition snapshots omit them; fixed numeric timeouts remain
|
|
158
|
+
in the snapshot.
|
|
105
159
|
|
|
106
160
|
### compute
|
|
107
161
|
|
|
@@ -147,10 +201,15 @@ command fails.
|
|
|
147
201
|
|
|
148
202
|
### checkpoint
|
|
149
203
|
|
|
150
|
-
Ends the run in a `waiting` state for human review.
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
204
|
+
Ends the run in a `waiting` state for human review. The checkpoint bundle is
|
|
205
|
+
terminal, so no process keeps running while the run waits. The human answers
|
|
206
|
+
with `/workflow answer <json>` (or plain text), which starts a **continuation
|
|
207
|
+
run**: a new run with its own bundle and trace, linked to the checkpointed run
|
|
208
|
+
through `parentRunId`. The continuation receives the answer as its input,
|
|
209
|
+
carries forward every output the parent produced (including the checkpoint's),
|
|
210
|
+
and continues routing along the checkpoint's outgoing edge. Outgoing edges
|
|
211
|
+
from checkpoint nodes are allowed exactly so continuations have somewhere to
|
|
212
|
+
go; step accounting carries over, so `maxSteps` bounds the whole chain.
|
|
154
213
|
|
|
155
214
|
```typescript
|
|
156
215
|
checkpoint({
|
|
@@ -207,6 +266,60 @@ A missing case for the resolved value fails the run with a routing error. A
|
|
|
207
266
|
node with no outgoing edge (or no matching failure route) ends the run:
|
|
208
267
|
`completed` on success, `failed`/`timed_out`/`cancelled` otherwise.
|
|
209
268
|
|
|
269
|
+
## Model workflow control
|
|
270
|
+
|
|
271
|
+
The model sees one `workflow` tool. Its `action` field supports:
|
|
272
|
+
|
|
273
|
+
- `list` for discovered workflow names and sources.
|
|
274
|
+
- `start` with a workflow name or path and structured input.
|
|
275
|
+
- `status` for the active run or a supplied run ID.
|
|
276
|
+
- `pause`, `resume`, and `cancel` for the active run.
|
|
277
|
+
- `answer` with checkpoint input and an optional run ID.
|
|
278
|
+
- `submit` for the current workflow step contract.
|
|
279
|
+
|
|
280
|
+
A model-started run is queued until the model's current turn settles. The first
|
|
281
|
+
workflow prompt then starts a new turn. This keeps the requesting turn outside
|
|
282
|
+
the workflow's first attempt and prevents an early missing-submission reminder.
|
|
283
|
+
The normal extension offers all actions. The headless RPC bridge offers only
|
|
284
|
+
`submit`, so a workflow child cannot recursively control other runs.
|
|
285
|
+
|
|
286
|
+
### Built-in monitor
|
|
287
|
+
|
|
288
|
+
The built-in `monitor` workflow turns a plain request for repeated checks into
|
|
289
|
+
one looping workflow run. Its input is:
|
|
290
|
+
|
|
291
|
+
```json
|
|
292
|
+
{
|
|
293
|
+
"task": "Check pull request 123",
|
|
294
|
+
"everyMinutes": 30,
|
|
295
|
+
"reportWhen": "Checks fail or the state changes materially",
|
|
296
|
+
"stopWhen": "The pull request is merged or closed",
|
|
297
|
+
"maxChecks": 1000,
|
|
298
|
+
"checkTimeoutMinutes": 60
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
The first check runs immediately. Each accepted check records a bounded current
|
|
303
|
+
observation and chooses whether to continue, report, or stop. A report uses a
|
|
304
|
+
separate agent node so its structured check result is validated before the user
|
|
305
|
+
sees the message. The next check can read the previous accepted observation.
|
|
306
|
+
|
|
307
|
+
Intervals must be whole minutes from 1 through 1,440. `maxChecks` defaults to
|
|
308
|
+
1,000 and cannot exceed 1,000. `checkTimeoutMinutes` is optional and applies to
|
|
309
|
+
check and report agent nodes. It must be from 5 through 1,440 minutes. Its
|
|
310
|
+
default is the larger of 60 minutes and `everyMinutes`. The workflow also has
|
|
311
|
+
a finite step limit and bounded observation and report sizes.
|
|
312
|
+
|
|
313
|
+
The interval uses the existing shell action to launch the current Node
|
|
314
|
+
executable with a timer. This works on every platform supported by Pi. The node
|
|
315
|
+
and command timeouts are higher than the maximum interval. Cancelling the
|
|
316
|
+
workflow aborts the timer process immediately. If the owning Pi process or
|
|
317
|
+
standalone host stops during the wait, normal parking rules abort the shell node
|
|
318
|
+
and resume later by running that wait again from the beginning.
|
|
319
|
+
|
|
320
|
+
A monitor uses the session's single active workflow slot. It does not provide
|
|
321
|
+
cron syntax, calendar scheduling, OS notifications, or a background service.
|
|
322
|
+
|
|
210
323
|
## The step contract
|
|
211
324
|
|
|
212
325
|
Every `agent` prompt ends with a step contract block naming the workflow, the
|
|
@@ -217,16 +330,17 @@ step id, the attempt id, and the expected output shape:
|
|
|
217
330
|
Workflow step contract (workflow: autoimplement, step: review, attempt: 6f9d…)
|
|
218
331
|
|
|
219
332
|
Complete this step by calling the `workflow` tool exactly once with:
|
|
220
|
-
{"step": "review", "attempt": "6f9d…", "output": <your result>}
|
|
333
|
+
{"action": "submit", "step": "review", "attempt": "6f9d…", "output": <your result>}
|
|
221
334
|
Expected output: { "route": "clean" | "issues_found", "reason": "short justification" }
|
|
222
335
|
The step is complete only after the workflow tool accepts the output.
|
|
223
336
|
If the tool reports a validation error, correct the output and call it again.
|
|
224
337
|
```
|
|
225
338
|
|
|
226
|
-
The `workflow` tool
|
|
227
|
-
rejected (with a reason the model sees) when no step
|
|
228
|
-
is wrong, the attempt id belongs to an earlier attempt
|
|
229
|
-
revisit node ids, so each attempt gets a fresh id), or
|
|
339
|
+
The `workflow` tool uses `{ action: "submit", step, attempt, output }` for step
|
|
340
|
+
results. Submissions are rejected (with a reason the model sees) when no step
|
|
341
|
+
is pending, the step id is wrong, the attempt id belongs to an earlier attempt
|
|
342
|
+
of the same node (loops revisit node ids, so each attempt gets a fresh id), or
|
|
343
|
+
`validate` throws.
|
|
230
344
|
Acceptance resolves the step and the engine advances; the next agent prompt
|
|
231
345
|
arrives as a new user message in the same conversation.
|
|
232
346
|
|
|
@@ -251,8 +365,9 @@ After the final run state has been persisted, the Pi extension sends the
|
|
|
251
365
|
presentation instructions and bounded final result to the model as a hidden
|
|
252
366
|
follow-up message. The next visible message is a normal assistant response.
|
|
253
367
|
Returning `undefined`, returning an empty string, or omitting
|
|
254
|
-
`presentationPrompt` produces no follow-up.
|
|
255
|
-
|
|
368
|
+
`presentationPrompt` produces no follow-up. Failed, timed-out, and cancelled
|
|
369
|
+
runs are never presented; the extension reports their persisted status and
|
|
370
|
+
error directly. Async prompt builders have 30 seconds to finish and receive an
|
|
256
371
|
`AbortSignal` that fires on timeout, session shutdown, or when a new workflow
|
|
257
372
|
or normal user turn starts; stale presentations are discarded. Once a presentation message has
|
|
258
373
|
been queued, another workflow cannot start until that assistant response
|
|
@@ -271,8 +386,10 @@ Runs execute one node at a time. Every transition is persisted to the run
|
|
|
271
386
|
bundle before the engine moves on, which is what makes the live viewer
|
|
272
387
|
possible. Defaults worth knowing:
|
|
273
388
|
|
|
274
|
-
- Node timeout is 15 minutes unless the node sets `timeoutMs
|
|
275
|
-
node has outcome `timed_out` and can
|
|
389
|
+
- Node timeout is 15 minutes unless the node sets `timeoutMs` to a positive
|
|
390
|
+
number or context callback. A timed-out node has outcome `timed_out` and can
|
|
391
|
+
be routed with `$result.outcome`. A timed-out agent node also aborts its Pi
|
|
392
|
+
turn, and late output for that attempt is rejected.
|
|
276
393
|
- `maxSteps` (workflow-level, default 100) bounds loops built from cycles in
|
|
277
394
|
the graph.
|
|
278
395
|
- `/workflow pause` requests a pause: the current step finishes normally,
|
|
@@ -290,6 +407,33 @@ possible. Defaults worth knowing:
|
|
|
290
407
|
- Agent nudges: if the model ends its turn without submitting the pending
|
|
291
408
|
step, it gets a reminder, twice by default, then the step fails.
|
|
292
409
|
|
|
410
|
+
## Workflows started by controllers
|
|
411
|
+
|
|
412
|
+
A controller can start a workflow as a finite child job with `ctx.workflows.ensure()`. The request key is stable across reconciliation passes, and the input fingerprint prevents one key from being reused for different work.
|
|
413
|
+
|
|
414
|
+
```typescript
|
|
415
|
+
const run = await ctx.workflows.ensure({
|
|
416
|
+
requestKey: `repair:${resource.metadata.generation}`,
|
|
417
|
+
workflow: "repair-pull-request",
|
|
418
|
+
input: { repository: resource.spec.repository, number: resource.spec.number },
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
if (run.state !== "succeeded") {
|
|
422
|
+
return ctx.requeueAfter(5_000, {
|
|
423
|
+
workflowRun: {
|
|
424
|
+
requestId: run.requestId,
|
|
425
|
+
...(run.runId ? { runId: run.runId } : {}),
|
|
426
|
+
state: run.state,
|
|
427
|
+
attempt: run.attempt,
|
|
428
|
+
},
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
Child workflow completion queues the parent resource again. A running child left by a stopped host is recorded as a failed run bundle with a `run_interrupted` event. The controller treats that child attempt as interrupted, and the next parent reconciliation starts another immutable attempt. Consequential external mutations should use the controller effect API so uncertain results are observed before retry.
|
|
434
|
+
|
|
435
|
+
See [CONTROLLERS.md](CONTROLLERS.md) for controller definitions and the full recovery contract.
|
|
436
|
+
|
|
293
437
|
## Using the engine outside pi
|
|
294
438
|
|
|
295
439
|
The engine is pi-agnostic. `WorkflowEngine` takes any `AgentStepExecutor`, so
|