@signalridge/pi-workflows 1.3.2 → 1.5.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/CHANGELOG.md +140 -0
- package/README.md +93 -22
- package/package.json +5 -4
- package/skills/workflow-authoring/SKILL.md +171 -18
- package/skills/workflow-authoring/examples/named-task-graph.js +34 -0
- package/skills/workflow-authoring/references/capabilities.md +4 -3
- package/skills/workflow-authoring/references/capability-details.md +28 -8
- package/skills/workflow-authoring/references/debugging.md +2 -2
- package/skills/workflow-authoring/references/focused-recipes.md +1 -0
- package/skills/workflow-authoring/references/lifecycle.md +3 -3
- package/skills/workflow-authoring/references/pattern-selection.md +1 -0
- package/skills/workflow-authoring/references/registry-ownership.md +6 -4
- package/skills/workflow-authoring/references/review.md +12 -7
- package/skills/workflow-authoring/references/runtime.md +6 -5
- package/skills/workflow-authoring/references/versions.md +1 -1
- package/skills/workflow-patterns/SKILL.md +72 -33
- package/skills/workflow-review/SKILL.md +70 -0
- package/src/builtins.ts +23 -23
- package/src/capabilities.ts +39 -7
- package/src/engine.ts +582 -142
- package/src/index.ts +413 -154
- package/src/journal.ts +447 -169
- package/src/orchestrator.ts +478 -0
- package/src/rpc-client.ts +37 -48
- package/src/runtime.ts +508 -167
- package/src/ui.ts +29 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,145 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.5.0
|
|
4
|
+
### Minor Changes
|
|
5
|
+
|
|
6
|
+
- c1b1741: Protocol v4: workflows use the Agent-tier catalogue directly.
|
|
7
|
+
|
|
8
|
+
The wire contract changes, so the three packages move together: `pi-workflows`
|
|
9
|
+
now declares `"@signalridge/pi-subagents": ">=1.9.0"`, and a peer outside that
|
|
10
|
+
range fails the startup handshake with a diagnostic naming both packages rather
|
|
11
|
+
than failing mid-run.
|
|
12
|
+
|
|
13
|
+
The `small | medium | large` workflow-tier vocabulary and its mapping layer are
|
|
14
|
+
gone. A managed request names an Agent tier — a key in the host's own
|
|
15
|
+
`agentTiers` catalogue — and `resolveAgentTier()` is the single resolver for
|
|
16
|
+
every spawn path. Removed with it: the wire's mapped `agentTier` field, the
|
|
17
|
+
`workflow` settings key (retired with a warning naming it; `agentTiers.defaultTier`
|
|
18
|
+
replaces `workflow.defaultTier`), and the per-call `model`/`thinking` selectors,
|
|
19
|
+
which the validator now rejects rather than accepting and ignoring.
|
|
20
|
+
|
|
21
|
+
Every v4 capability is required and every ping carries the host's tier catalogue,
|
|
22
|
+
so an incomplete peer fails one check instead of five optional ones. Fresh
|
|
23
|
+
installs ship `low`/`medium`/`high` profiles, all inheriting their model, and a
|
|
24
|
+
managed call that names no tier falls back to `medium`, so a workflow runs on an
|
|
25
|
+
unconfigured machine without this package choosing a vendor. Because `medium`
|
|
26
|
+
inherits, that fallback lands on the parent session's model — it commits to an
|
|
27
|
+
effort level, not to a vendor, so it does change the thinking level a managed
|
|
28
|
+
call runs at when the parent session is set higher, and it does not move the
|
|
29
|
+
work anywhere cheaper. What it buys is a named policy with a durable snapshot
|
|
30
|
+
and a scope check; a workspace that wants cheaper managed work names a
|
|
31
|
+
`defaultTier` whose profile pins a model. The fallback is scoped to calls that
|
|
32
|
+
cannot inherit a parent model rather than being installed as
|
|
33
|
+
`agentTiers.defaultTier`: a shipped catalogue default would have applied to
|
|
34
|
+
every ordinary `Agent` spawn as well, silencing `defaultModel` and pinning a
|
|
35
|
+
thinking level on machines that configured neither. The catalogue no longer
|
|
36
|
+
ships a `fast` profile; it was the same (model, thinking) pair as `low`, and the
|
|
37
|
+
one agent that named it (Explore) now names `low`. A user agent file that still
|
|
38
|
+
says `agentTier: fast` is reported by the existing unknown-reference check when
|
|
39
|
+
settings and agents load, rather than failing at its first spawn.
|
|
40
|
+
|
|
41
|
+
`agentTiers.defaultTier` therefore has three states, and `/agents → Settings`
|
|
42
|
+
offers all three rather than rendering two of them as one word: a named tier,
|
|
43
|
+
`unset` (managed calls reach the shipped fallback), and `none` (recorded as
|
|
44
|
+
`noDefaultTier`, which withdraws the fallback so managed calls fail closed).
|
|
45
|
+
`setDefaultAgentTier` takes that choice as a tagged value, so a caller cannot
|
|
46
|
+
express "no default" without saying which of the two it means.
|
|
47
|
+
|
|
48
|
+
`resolveAgentTier` gained `requireTier` for that fallback, and the two things
|
|
49
|
+
pre-resolution callers need are both answered by the resolver rather than
|
|
50
|
+
restated beside it: `agentTierApplies()` for "will a tier own this spawn's
|
|
51
|
+
model?", and `selectAgentTier()` for the tier key itself, which the managed
|
|
52
|
+
path needs to label a tombstone and the lifecycle events before the runner has
|
|
53
|
+
resolved anything.
|
|
54
|
+
|
|
55
|
+
Because a tier now owns model resolution outright, the spawn paths stop
|
|
56
|
+
pre-resolving one — and with it they stopped producing the model name the agent
|
|
57
|
+
UI shows. The resolution callback carries that label back instead, so a tier
|
|
58
|
+
that pins a model still names it in the viewer and the agent list, and a profile
|
|
59
|
+
that inherits correctly shows none. The label rides beside the snapshot rather
|
|
60
|
+
than inside it: the snapshot is a durable policy record a managed tombstone
|
|
61
|
+
persists and revalidates, and a cosmetic string does not belong in it.
|
|
62
|
+
|
|
63
|
+
Workflow resume now keys each cached call on the policy for that call's own tier
|
|
64
|
+
rather than on a whole-catalogue fingerprint, so defining or editing an unrelated
|
|
65
|
+
tier no longer forces a full re-execution — but only for a call that names its
|
|
66
|
+
tier. A call that names none keeps the whole-catalogue key, because the host
|
|
67
|
+
resolves those as `call > agent frontmatter > defaultTier` and frontmatter is
|
|
68
|
+
not on this wire: an agent reaches a managed caller as a name. Keyed on
|
|
69
|
+
`defaultTier`, such a call would be wrong twice over — replaying stale work
|
|
70
|
+
after an edit to the tier its agent actually declares, and re-executing after an
|
|
71
|
+
edit to a default it never reached. `agentTierPolicyIdentity()` therefore
|
|
72
|
+
requires a tier rather than accepting `undefined` and folding the default in, so
|
|
73
|
+
the wrong call cannot be written. Journal schema v4 and managed tombstone schema
|
|
74
|
+
v2 are quarantined from older facts rather than migrated.
|
|
75
|
+
|
|
76
|
+
The tier catalogue belongs to the user, names included. The built-in workflows
|
|
77
|
+
and the ad-hoc script ship with this package, so a tier name they use that the
|
|
78
|
+
host does not define is dropped in favour of the host's default rather than
|
|
79
|
+
failing the run — they cannot assert which names exist on someone else's
|
|
80
|
+
machine. A script the user wrote still fails closed on an undefined tier, since
|
|
81
|
+
that is a typo in a catalogue they own. Shipped-ness travels with the script
|
|
82
|
+
rather than with the frame that called it, so a user script that reaches a
|
|
83
|
+
built-in through `workflow(name)` applies the built-in's rule to the built-in;
|
|
84
|
+
the authoring skill now says outright that `low`/`medium`/`high` are the shipped
|
|
85
|
+
profiles rather than names an authored script may assume.
|
|
86
|
+
|
|
87
|
+
The managed routing policy published on the wire reports the default a managed
|
|
88
|
+
call will actually get, fallback included, so a peer's replay identity cannot
|
|
89
|
+
disagree with the host's selection; its sorts are code-unit rather than
|
|
90
|
+
locale-dependent, so the same catalogue fingerprints identically everywhere. The
|
|
91
|
+
workflow peer re-pings per start and resume instead of pinning the catalogue it
|
|
92
|
+
saw at session start, and reports the host-selected tier back through an
|
|
93
|
+
`onTierResolved` callback rather than by mutating the dispatch options.
|
|
94
|
+
|
|
95
|
+
Fixes: a nested spawn no longer relabels an agent's frontmatter tier as
|
|
96
|
+
caller-requested, which had turned a `scopeModels` warning into a refusal and
|
|
97
|
+
misattributed the choice; the scheduler no longer freezes a frontmatter tier into
|
|
98
|
+
a job, so editing the agent file takes effect at fire time; retired managed spawn
|
|
99
|
+
keys are now bounded instead of growing for the life of a session; a
|
|
100
|
+
scope refusal for a tier whose profile inherits its model now names the model
|
|
101
|
+
that would have run instead of the literal `undefined`; and the retired
|
|
102
|
+
`workflow` key warns once per process rather than on every settings read and
|
|
103
|
+
every project write.
|
|
104
|
+
|
|
105
|
+
The tier-key predicate and its length bound now have a single definition, in
|
|
106
|
+
`pi-subagents-protocol`, which `pi-subagents` re-exports. The wire is the
|
|
107
|
+
narrower of the two gates — a key one accepted and the other rejected could
|
|
108
|
+
never reach a peer — so a second copy could only ever be a way for them to
|
|
109
|
+
disagree. That one definition now also governs `blockedProfiles`, which was
|
|
110
|
+
being validated by a helper that trims first: `" low"` became `"low"` there
|
|
111
|
+
while the same value in `profiles` or `defaultTier` was refused outright. All
|
|
112
|
+
three reject.
|
|
113
|
+
|
|
114
|
+
`/agents → Settings` no longer names the fallback tier in the `unset` row's
|
|
115
|
+
label. Which tier `unset` reaches depends on the catalogue, and a workspace that
|
|
116
|
+
edits or deletes the shipped `medium` profile leaves it reaching nothing — at
|
|
117
|
+
which point `unset` and `none` behave identically. The row's description asks
|
|
118
|
+
the resolver what `unset` would currently resolve to and says that, including
|
|
119
|
+
when the answer is "nothing".
|
|
120
|
+
|
|
121
|
+
`pi-workflows` also gained the session-lifecycle handling this routing work
|
|
122
|
+
needed to sit on. The engine has an explicit dispose lifecycle, so a protocol
|
|
123
|
+
probe or a start that is in flight when a session ends rejects with a disposed
|
|
124
|
+
error instead of resolving against an engine nobody owns. The tool, command and
|
|
125
|
+
widget surfaces register exactly once and resolve the *current* engine on each
|
|
126
|
+
use, rather than closing over the one that existed when they were registered —
|
|
127
|
+
a session replacement previously left them pointing at a disposed engine. Tool
|
|
128
|
+
and command handlers report failures as results rather than throwing out of the
|
|
129
|
+
host's dispatch, and the lifecycle handler accepts `subagents:created` and
|
|
130
|
+
`subagents:started` so a spawn's identity is recorded from the first event that
|
|
131
|
+
carries it rather than only at completion.
|
|
132
|
+
|
|
133
|
+
### Patch Changes
|
|
134
|
+
|
|
135
|
+
- Updated dependencies [c1b1741]
|
|
136
|
+
- @signalridge/pi-subagents-protocol@1.4.0
|
|
137
|
+
|
|
138
|
+
## 1.4.0
|
|
139
|
+
### Minor Changes
|
|
140
|
+
|
|
141
|
+
- 28c8aa1: Strengthen script orchestration with deterministic named dependency graphs, bounded task retries and failure policies, safer fan-out barriers, durable nested-workflow replay, and reliable pause/resume accounting. The natural-language `/workflows run` path now plans, executes, and synthesizes a bounded task graph, while expanded authoring/pattern guidance and the new `workflow-review` skill make routing, recovery, and publication constraints explicit.
|
|
142
|
+
|
|
3
143
|
## 1.3.2
|
|
4
144
|
### Patch Changes
|
|
5
145
|
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
JavaScript orchestration for Pi. A workflow is a raw JavaScript module executed
|
|
4
4
|
in a determinism-guarded `node:vm` realm; the runtime supplies `agent()`,
|
|
5
|
-
`parallel()`, `pipeline()`, `workflow()`, quality helpers (`verify`,
|
|
5
|
+
`parallel()`, `pipeline()`, `orchestrate()`, `workflow()`, quality helpers (`verify`,
|
|
6
6
|
`judgePanel`, `loopUntilDry`, `completenessCheck`, `retry`, `gate`), human
|
|
7
7
|
`checkpoint()`, `phase()`, `log()`, `args`, `cwd`, restricted `process`, and
|
|
8
8
|
`budget`. Every live `agent()` call is dispatched through the additive
|
|
@@ -12,8 +12,8 @@ isolation, retry, and session policy.
|
|
|
12
12
|
|
|
13
13
|
## Install
|
|
14
14
|
|
|
15
|
-
`@signalridge/pi-workflows` requires `@signalridge/pi-subagents` `>=1.
|
|
16
|
-
with protocol
|
|
15
|
+
`@signalridge/pi-workflows` requires `@signalridge/pi-subagents` `>=1.9.0`
|
|
16
|
+
with protocol v4 managed-spawn, Agent-tier, and managed-policy support. They are separate Pi packages: the peer
|
|
17
17
|
dependency documents the requirement but intentionally does not auto-load or
|
|
18
18
|
duplicate the subagent extension.
|
|
19
19
|
|
|
@@ -54,9 +54,9 @@ export const meta = {
|
|
|
54
54
|
|
|
55
55
|
phase("scan");
|
|
56
56
|
const findings = await parallel([
|
|
57
|
-
() => agent("Check for cross-package imports", { label: "imports", tier: "
|
|
58
|
-
() => agent("Check for secrets", { label: "secrets", tier: "
|
|
59
|
-
() => agent("Check for dead code", { label: "dead-code", tier: "
|
|
57
|
+
() => agent("Check for cross-package imports", { label: "imports", tier: "low" }),
|
|
58
|
+
() => agent("Check for secrets", { label: "secrets", tier: "low" }),
|
|
59
|
+
() => agent("Check for dead code", { label: "dead-code", tier: "low" }),
|
|
60
60
|
]);
|
|
61
61
|
|
|
62
62
|
phase("synthesize");
|
|
@@ -69,29 +69,42 @@ return report;
|
|
|
69
69
|
The meta statement must be the first statement and use only literals — spread,
|
|
70
70
|
computed keys, methods, template interpolation, and the reserved keys
|
|
71
71
|
`__proto__`/`constructor`/`prototype` are rejected. `Date.now()`,
|
|
72
|
-
`Math.random()`, and no-argument `new Date()` are unavailable (
|
|
73
|
-
plus in-realm stubs);
|
|
72
|
+
`Math.random()`, and no-argument `new Date()` are unavailable (an AST precheck
|
|
73
|
+
plus in-realm stubs); prompt text containing those words remains valid. Pass
|
|
74
|
+
timestamps and randomness through `args`. The vm is
|
|
74
75
|
a determinism guardrail, not a security boundary — scripts are generated by the
|
|
75
76
|
user's own LLM and run on the user's own machine.
|
|
76
77
|
|
|
77
78
|
### Runtime globals
|
|
78
79
|
|
|
79
|
-
- `agent(prompt, { label, phase, schema,
|
|
80
|
+
- `agent(prompt, { label, phase, schema, tier, isolation, thread, agentType, toolset, excludeTools, timeoutMs, retries })`
|
|
80
81
|
— dispatch one subagent through the managed spawn protocol. Returns text, a
|
|
81
|
-
schema-validated value, or recoverable `null` after retries.
|
|
82
|
+
schema-validated value, or recoverable `null` after retries. Replayed journal
|
|
83
|
+
calls consume no real-dispatch cap or token/phase budget. `schema` is a
|
|
82
84
|
plain JSON Schema; the reply is parsed and validated client-side with bounded
|
|
83
85
|
repair across attempts, and exhaustion throws `SCHEMA_NONCOMPLIANCE`.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
86
|
+
`tier` is the provider-neutral workflow route; `thread` chains sequential turns
|
|
87
|
+
within a named conversation thread. A legacy programmatic `model` option may
|
|
88
|
+
still be accepted by non-managed callers, but workflow meta/phase model or
|
|
89
|
+
thinking fields are rejected and are never a workflow policy source. Pre-schema-v4
|
|
90
|
+
journal runs are quarantined and cannot be resumed.
|
|
87
91
|
- `parallel(thunks)` — run thunks concurrently, preserve input order. Recoverable
|
|
88
92
|
thunk failures become `null`; non-recoverable failures (token budget, agent
|
|
89
|
-
limit) halt the run. Fan-out that breaches
|
|
90
|
-
batch.
|
|
93
|
+
limit) halt the run after the batch barrier settles. Fan-out that breaches
|
|
94
|
+
`maxAgents` cancels only its own batch; each call accepts at most 4096 thunks.
|
|
91
95
|
- `pipeline(items, ...stages)` — items run concurrently, stages per item run
|
|
92
|
-
sequentially with `(previousValue, originalItem, index)
|
|
96
|
+
sequentially with `(previousValue, originalItem, index)`; each call accepts at
|
|
97
|
+
most 4096 items and waits for the batch before surfacing fatal failures.
|
|
98
|
+
- `orchestrate(tasks, { onError })` — execute a named dependency graph in
|
|
99
|
+
deterministic declaration-order layers. Each task is `{ id, dependsOn?, phase?,
|
|
100
|
+
retries?, run }`; its callback receives `{ id, attempt, results, statuses }`.
|
|
101
|
+
`onError` is `skip-dependents` (default), `continue`, or `fail-fast`. Results
|
|
102
|
+
are keyed by task id, and task lifecycle events are observable through
|
|
103
|
+
`onRuntimeEvent`.
|
|
93
104
|
- `workflow(savedName, childArgs?)` — run a saved workflow inline; one nested
|
|
94
|
-
level, sharing the limiter, counters, token accounting, and store.
|
|
105
|
+
level, sharing the limiter, counters, token accounting, and store. The child
|
|
106
|
+
result is journaled as one parent call with a generation-scoped namespace, so
|
|
107
|
+
unchanged nested work replays without spawning again after resume.
|
|
95
108
|
- `verify(item, { reviewers, threshold, lens })`, `judgePanel(attempts,
|
|
96
109
|
{ judges, rubric })`, `loopUntilDry({ round, key, consecutiveEmpty,
|
|
97
110
|
maxRounds })`, `completenessCheck(taskArgs, results)`, `retry(thunk,
|
|
@@ -107,17 +120,35 @@ user's own LLM and run on the user's own machine.
|
|
|
107
120
|
### Resume
|
|
108
121
|
|
|
109
122
|
A script edit replays the longest unchanged prefix from the journal and runs
|
|
110
|
-
live from the first changed or inserted `agent()`/`checkpoint()` call.
|
|
111
|
-
`
|
|
123
|
+
live from the first changed or inserted `agent()`/`checkpoint()` call. The
|
|
124
|
+
original invocation `args` and execution limits are frozen and reused on manual
|
|
125
|
+
or automatic resume. Pass `resumeFromRunId` with the edited script; unchanged
|
|
126
|
+
calls report zero tokens,
|
|
112
127
|
and edited calls rotate their managed spawn key generation so pi-subagents
|
|
113
128
|
never raises a fingerprint conflict.
|
|
114
129
|
|
|
130
|
+
## Skills
|
|
131
|
+
|
|
132
|
+
The package ships three model-routable skills:
|
|
133
|
+
|
|
134
|
+
- `workflow-authoring` — design, review, and debug custom JavaScript workflows;
|
|
135
|
+
it teaches topology selection, stable work IDs, bounded failure handling, and
|
|
136
|
+
resume-safe authoring.
|
|
137
|
+
- `workflow-patterns` — choose and invoke a reviewed built-in for research,
|
|
138
|
+
adversarial review, code review, multi-perspective analysis, or codebase audit.
|
|
139
|
+
- `workflow-review` — audit an existing workflow for topology, data flow,
|
|
140
|
+
recovery, resume safety, and publication readiness without editing it.
|
|
141
|
+
|
|
142
|
+
Use the pattern skill for a standard task, the authoring skill for a custom
|
|
143
|
+
workflow, and the review skill before accepting a topology or recovery change.
|
|
144
|
+
|
|
115
145
|
## Tools and commands
|
|
116
146
|
|
|
117
147
|
- `workflow`: run a script (`script`) or a saved/built-in workflow (`name`),
|
|
118
148
|
with `args`, `background` (default true), `maxAgents`, `concurrency`,
|
|
119
149
|
`agentRetries`, `tokenBudget`, `agentTimeoutMs`, and `resumeFromRunId`.
|
|
120
150
|
- `workflow_control`: `list`, `get`, `pause`, `resume`, `stop`, or `rm` a run; `rm` writes a durable removal tombstone.
|
|
151
|
+
- `pi.events` emits `pi-workflows:runtime` snapshots (`{ runId, event }`) for phase, task, quality, retry, and nested-workflow progress; observer failures are contained.
|
|
121
152
|
- `/workflows`: TUI navigator plus `run`, `status`, `watch`, `stop`, `pause`,
|
|
122
153
|
`resume`, `rm`, and `save <name> [runId]` subcommands.
|
|
123
154
|
- `/workflows-models`: points at pi-subagents' `/agents → Model tiers` — there
|
|
@@ -155,12 +186,52 @@ written atomically with a backup.
|
|
|
155
186
|
|
|
156
187
|
## Model routing
|
|
157
188
|
|
|
158
|
-
|
|
189
|
+
`tier` names a key in the host's own `agentTiers` catalogue — the same catalogue an
|
|
190
|
+
ordinary `Agent` call uses. There is no separate workflow-tier vocabulary and no
|
|
191
|
+
mapping layer: a workflow that wants cheap work asks for the tier the user defined
|
|
192
|
+
for cheap work.
|
|
193
|
+
|
|
194
|
+
```js
|
|
195
|
+
await agent("summarize this diff", { tier: "low" })
|
|
196
|
+
await agent("design the migration", { tier: "high" })
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
The host's catalogue arrives with the protocol handshake, so a tier this host does
|
|
200
|
+
not define is rejected before any dispatch, naming the ones it does. Resolution
|
|
201
|
+
itself is entirely pi-subagents' `resolveAgentTier()`: it owns the final model,
|
|
202
|
+
thinking level, clamping, availability checks, and the immutable resolution
|
|
203
|
+
snapshot.
|
|
204
|
+
|
|
205
|
+
The catalogue belongs to whoever runs the workflow — the names included. The
|
|
206
|
+
built-in workflows and the ad-hoc script ship with this package, so they cannot
|
|
207
|
+
assert that `low` exists on a machine whose tiers are called `cheap` and `deep`:
|
|
208
|
+
a name they use that the host does not define is dropped, with a log line, and
|
|
209
|
+
the host's own default applies. A script you wrote names tiers from your own
|
|
210
|
+
catalogue, so an undefined one there is a typo and fails closed instead.
|
|
211
|
+
|
|
212
|
+
There is no per-call `model` or `thinking`, and `meta`/phase metadata may not carry
|
|
213
|
+
them either — both are rejected explicitly rather than accepted and ignored. A tier
|
|
214
|
+
is the only model policy a workflow can express, which is what keeps a script from
|
|
215
|
+
pinning a vendor the host did not choose.
|
|
216
|
+
|
|
217
|
+
Omitting `tier` uses the agent's own tier, then `agentTiers.defaultTier`. Fresh
|
|
218
|
+
installs ship `low`/`medium`/`high` profiles, all inheriting their model, and a
|
|
219
|
+
managed call that still names no tier falls back to `medium` — so the built-ins run
|
|
220
|
+
on a new machine without any configuration. That fallback is scoped to managed
|
|
221
|
+
calls: it is not the catalogue's `defaultTier`, so an ordinary `Agent` spawn on the
|
|
222
|
+
same machine keeps falling through to `defaultModel` and the parent session. A host
|
|
223
|
+
whose default has been cleared (`noDefaultTier`) rejects an untiered managed call
|
|
224
|
+
rather than quietly inheriting the parent session's model.
|
|
225
|
+
|
|
226
|
+
The managed wire, invocation record, journal, tombstone, and resume state all carry
|
|
227
|
+
the one resolved tier — including one the host defaulted to, so a run's record says
|
|
228
|
+
what actually ran. Resume keys each cached call on the policy for *that call's*
|
|
229
|
+
tier: editing an unrelated tier does not invalidate work that never used it.
|
|
159
230
|
|
|
160
231
|
## Intentional adaptations from upstream
|
|
161
232
|
|
|
162
233
|
- **No host-side web tools.** Agents reach the web through configured pi-web-access/MCP tools or an agent type's tool policy.
|
|
163
|
-
- **Agent registry and tier catalogue ownership.** `agentType` and
|
|
234
|
+
- **Agent registry and tier catalogue ownership.** `agentType` and the tier catalogue resolve in pi-subagents; the workflow package supplies only a tier key, avoiding a second model/thinking policy.
|
|
164
235
|
- **Schema validation is client-side.** The managed request does not assume a structured-output tool; the runtime asks for JSON, parses, validates (including `additionalProperties`), and repairs across bounded attempts.
|
|
165
236
|
|
|
166
|
-
State is persisted as `pi.appendEntry("pi-workflows:journal", ...)` custom entries (schema
|
|
237
|
+
State is persisted as `pi.appendEntry("pi-workflows:journal", ...)` custom entries (schema v4), including frozen invocation args, resolved tier identity, terminal result previews, script revisions, named nested-workflow result boundaries, and durable removals. Interrupted/provider-limited runs replay from the journal; pre-schema-v4 journals are quarantined rather than replayed. Foreground `AbortSignal`s stop owned children through explicit owner-scoped stop/quiescence, and dispose/branch replacement reject stale waiters.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@signalridge/pi-workflows",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Deterministic JavaScript orchestration workflows backed by pi-subagents managed spawning.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -17,10 +17,10 @@
|
|
|
17
17
|
"peerDependencies": {
|
|
18
18
|
"@earendil-works/pi-coding-agent": "^0.84.0",
|
|
19
19
|
"@earendil-works/pi-tui": "^0.84.0",
|
|
20
|
-
"@signalridge/pi-subagents": ">=1.
|
|
20
|
+
"@signalridge/pi-subagents": ">=1.9.0"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@signalridge/pi-subagents-protocol": "^1.
|
|
23
|
+
"@signalridge/pi-subagents-protocol": "^1.4.0",
|
|
24
24
|
"@sinclair/typebox": "^0.34.50",
|
|
25
25
|
"acorn": "^8.16.0"
|
|
26
26
|
},
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
],
|
|
31
31
|
"skills": [
|
|
32
32
|
"skills/workflow-authoring",
|
|
33
|
-
"skills/workflow-patterns"
|
|
33
|
+
"skills/workflow-patterns",
|
|
34
|
+
"skills/workflow-review"
|
|
34
35
|
]
|
|
35
36
|
},
|
|
36
37
|
"repository": {
|
|
@@ -1,30 +1,183 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: workflow-authoring
|
|
3
|
-
description:
|
|
3
|
+
description: Design, write, review, and debug Pi JavaScript workflows that coordinate multiple subagents. Use when a request needs named dependencies, bounded fan-out, per-item pipelines, quality gates, saved or nested workflows, pause/resume, or workflow progress; do not use it merely to run an existing named workflow.
|
|
4
4
|
metadata:
|
|
5
|
-
version: "1.
|
|
5
|
+
version: "1.5.0"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Workflow authoring
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
A workflow is a small program that coordinates subagents; it is not a long prompt
|
|
11
|
+
that asks one agent to do every step. Keep semantic work in agents and keep
|
|
12
|
+
identity, ordering, bounds, failure handling, and aggregation deterministic in
|
|
13
|
+
JavaScript.
|
|
11
14
|
|
|
12
|
-
##
|
|
15
|
+
## Operating contract
|
|
13
16
|
|
|
14
|
-
|
|
17
|
+
- Use a workflow only when the task benefits from multiple agents, explicit
|
|
18
|
+
stages, independent coverage, or a quality/recovery loop. One or two simple
|
|
19
|
+
delegations should use the ordinary agent surface.
|
|
20
|
+
- Start every script with literal `export const meta = { name, description,
|
|
21
|
+
phases? }`. Return plain JSON data explicitly.
|
|
22
|
+
- Use `orchestrate()` for named dependencies, `parallel()` for a small
|
|
23
|
+
independent barrier, and `pipeline()` for independent items that each need a
|
|
24
|
+
sequential stage chain. Do not encode a dependency graph only in comments.
|
|
25
|
+
- Give every work unit a stable ID and every agent call a short unique label.
|
|
26
|
+
Keep IDs beside results before filtering; `null` is missing coverage, not a
|
|
27
|
+
successful finding.
|
|
28
|
+
- Bound graph tasks, fan-out items, loops, retries, agents, concurrency, and
|
|
29
|
+
evidence size. Invocation-level token and time caps are user constraints, not
|
|
30
|
+
substitutes for algorithmic bounds.
|
|
31
|
+
- Route an agent call with `tier: "<key>"`, naming a tier the destination host
|
|
32
|
+
defines. Omitting `tier` uses the agent's own tier, then the host's configured
|
|
33
|
+
default; a host whose default has been cleared rejects the call rather than
|
|
34
|
+
inheriting the parent model. There is no per-call `model`/`thinking`, and
|
|
35
|
+
neither may appear in workflow meta or phase profiles. Never guess a tier or
|
|
36
|
+
agent-type name—use names supplied by the current environment and follow
|
|
37
|
+
[registry ownership](references/registry-ownership.md).
|
|
38
|
+
- Workflows have no imports, filesystem/network APIs, timers, or unrestricted
|
|
39
|
+
Node APIs. Pass timestamps, randomness, and external decisions through `args`.
|
|
15
40
|
|
|
16
|
-
|
|
17
|
-
- **Helper task:** read [quality helpers](references/quality-helpers.md) only for `verify` or `judgePanel`, the [retry helper](references/retry-helper.md) only for `retry`, and [specialized helpers](references/specialized-helpers.md) only for `completenessCheck`, `loopUntilDry`, `gate`, or `checkpoint`.
|
|
18
|
-
- **Review:** use the [review checklist](references/review.md), plus only the matching [quality](references/quality-helpers.md) or [specialized](references/specialized-helpers.md) helper contracts.
|
|
19
|
-
- **Debug:** use the [debugging map](references/debugging.md).
|
|
20
|
-
- **Routing:** read [registry ownership](references/registry-ownership.md) before using `model`, `tier`, phase models, or `agentType`; use environment-specific names only when context supplies them.
|
|
21
|
-
- **Exact lookup or portability:** start with the generated [capability index](references/capabilities.md). Follow its exhaustive-facts pointer only for constraints or support boundaries. Use [versions](references/versions.md) when moving scripts between installations.
|
|
41
|
+
## The authoring pass
|
|
22
42
|
|
|
23
|
-
|
|
43
|
+
1. **Classify the topology.** Decide whether the work is direct, a named DAG,
|
|
44
|
+
homogeneous fan-out, a per-item pipeline, iterative discovery, or a quality
|
|
45
|
+
gate. Read [pattern selection](references/pattern-selection.md) only for the
|
|
46
|
+
chosen shape.
|
|
47
|
+
2. **Declare inputs and bounds.** Read `args` once, normalize the input, reject
|
|
48
|
+
or cap untrusted cardinality, and decide what incomplete coverage means.
|
|
49
|
+
3. **Write the envelope.** Declare only phases that are actually entered. Use
|
|
50
|
+
`phase(title, { budget })` for a soft token sub-budget, not as a scheduler.
|
|
51
|
+
4. **Build the smallest work ledger.** Store `{ id, status, result, error }`
|
|
52
|
+
(or an equivalent object) for each intended unit. Do not lose identity when
|
|
53
|
+
applying `.filter(Boolean)`.
|
|
54
|
+
5. **Dispatch with the right primitive.** Await every batch. For a dependent
|
|
55
|
+
prompt, include both the dependency ID and its actual result; do not assume
|
|
56
|
+
the child can see the parent conversation.
|
|
57
|
+
6. **Add quality only where it changes a decision.** Use a schema before reading
|
|
58
|
+
structured fields, then use `verify`, `judgePanel`, `gate`, or
|
|
59
|
+
`completenessCheck` with explicit bounds.
|
|
60
|
+
7. **Aggregate and report.** Return a bounded JSON result containing the answer,
|
|
61
|
+
coverage, and important failures. If the workflow runs in the background,
|
|
62
|
+
the run ID is the handle for later control/resume.
|
|
63
|
+
8. **Review the script.** Use [the review checklist](references/review.md), then
|
|
64
|
+
test with deterministic fake agents when changing topology or recovery.
|
|
24
65
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
66
|
+
## Topology rules
|
|
67
|
+
|
|
68
|
+
### Named dependencies: `orchestrate`
|
|
69
|
+
|
|
70
|
+
Use a task graph when a later step consumes a named earlier step:
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
const graph = await orchestrate([
|
|
74
|
+
{ id: "scan", run: () => agent("Inspect the target", { label: "scan" }) },
|
|
75
|
+
{ id: "security", run: () => agent("Check security risks", { label: "security" }) },
|
|
76
|
+
{
|
|
77
|
+
id: "report",
|
|
78
|
+
dependsOn: ["scan", "security"],
|
|
79
|
+
run: ({ results, statuses }) =>
|
|
80
|
+
agent(
|
|
81
|
+
"Synthesize the named findings; mention unavailable coverage.\n\n" +
|
|
82
|
+
JSON.stringify({ results, statuses }),
|
|
83
|
+
{ label: "report" },
|
|
84
|
+
),
|
|
85
|
+
},
|
|
86
|
+
]);
|
|
87
|
+
return graph;
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`orchestrate()` validates IDs, missing dependencies, duplicate edges, and
|
|
91
|
+
cycles before callbacks run. It executes ready tasks in declaration-order
|
|
92
|
+
layers with a barrier between layers and returns `{ results, tasks }`. Each task
|
|
93
|
+
callback receives detached `{ id, attempt, results, statuses }` snapshots.
|
|
94
|
+
`retries` retries ordinary task-callback failures up to three times. The default
|
|
95
|
+
`onError: "skip-dependents"` prevents a failed dependency from feeding a stale
|
|
96
|
+
prompt; use `onError: "continue"` only when the dependent explicitly handles
|
|
97
|
+
failed statuses. `fail-fast` stops discovery of later layers after the current
|
|
98
|
+
layer settles.
|
|
99
|
+
|
|
100
|
+
### Independent fan-out: `parallel`
|
|
101
|
+
|
|
102
|
+
Pass thunks, not already-started promises. Results preserve input order and a
|
|
103
|
+
recoverable item failure becomes `null`; fatal errors surface after the whole
|
|
104
|
+
batch barrier settles. Keep a ledger with IDs before filtering:
|
|
105
|
+
|
|
106
|
+
```js
|
|
107
|
+
const work = items.map((item) => ({ id: item.id, item }));
|
|
108
|
+
const outputs = await parallel(work.map(({ item }) => () =>
|
|
109
|
+
agent("Handle item " + item.id, { label: "item-" + item.id }),
|
|
110
|
+
));
|
|
111
|
+
const ledger = work.map(({ id }, index) => ({ id, result: outputs[index] }));
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Each `parallel()` call accepts at most 4096 thunks. It is a barrier: do not
|
|
115
|
+
start synthesis until the returned array is complete.
|
|
116
|
+
|
|
117
|
+
### Per-item stages: `pipeline`
|
|
118
|
+
|
|
119
|
+
Use `pipeline(items, ...stages)` when every item follows the same chain. Items
|
|
120
|
+
run concurrently, but stages for one item are sequential and receive
|
|
121
|
+
`(previousValue, originalItem, index)`. A stage failure drops that item to
|
|
122
|
+
`null`; guard missing values before the next stage. There is no cross-item
|
|
123
|
+
barrier between stages, and each call accepts at most 4096 items.
|
|
124
|
+
|
|
125
|
+
### Saved or nested workflows
|
|
126
|
+
|
|
127
|
+
`workflow(name, childArgs)` resolves a context-supplied saved workflow and runs
|
|
128
|
+
it inline with the parent's limiter, agent accounting, token accounting, and
|
|
129
|
+
store. One nested level is allowed; concurrent siblings are allowed. An
|
|
130
|
+
unchanged child is replayed as one parent journal call, while a changed or
|
|
131
|
+
interrupted child receives a generation-scoped managed identity. Do not use
|
|
132
|
+
nesting as an unbounded recursion mechanism.
|
|
133
|
+
|
|
134
|
+
## Prompt and result discipline
|
|
135
|
+
|
|
136
|
+
Every child prompt must be self-contained: state the role, exact task, relevant
|
|
137
|
+
inputs, expected output shape, and what the child must not do. Prefer a small
|
|
138
|
+
`schema` for values JavaScript will inspect. When a child consumes another
|
|
139
|
+
child's output, include the stable ID and actual bounded data, for example:
|
|
140
|
+
|
|
141
|
+
```js
|
|
142
|
+
const prompt = "Review result " + JSON.stringify({ id: "scan", result: scan });
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Do not pass whole transcripts when a summary or structured fields are enough.
|
|
146
|
+
Use `log()` for progress and return a result that distinguishes completed work,
|
|
147
|
+
missing coverage, and failed/skipped work.
|
|
148
|
+
|
|
149
|
+
## Failure, limits, and recovery
|
|
150
|
+
|
|
151
|
+
- Recoverable child failures return `null` after agent retries. Fatal workflow
|
|
152
|
+
errors (invalid hooks, exhausted hard limits, unsupported selectors, and
|
|
153
|
+
schema exhaustion) throw and must not be dissolved into a normal result.
|
|
154
|
+
- Runtime `agent({ retries })` retries execution failures. `orchestrate` task
|
|
155
|
+
`retries` retries the callback. Semantic `retry()`/`gate()` attempts are a
|
|
156
|
+
third, intentional layer. Bound and ledger every layer separately.
|
|
157
|
+
- `agent()` schema repair includes concrete validation feedback in the next
|
|
158
|
+
attempt. Do not read fields from an unvalidated text response.
|
|
159
|
+
- `checkpoint()` is headless by default for background runs. Use
|
|
160
|
+
`background: false` only when a human decision must be shown.
|
|
161
|
+
- `budget` and phase budgets are soft pre-call gates; concurrent work may
|
|
162
|
+
overshoot. Do not promise an exact token ceiling unless the caller supplied
|
|
163
|
+
and accepted that policy.
|
|
164
|
+
|
|
165
|
+
## Resume and publication gate
|
|
166
|
+
|
|
167
|
+
Resume replays the longest unchanged prefix of journaled calls. Keep call order,
|
|
168
|
+
labels, prompts, routing options, and inputs stable. `orchestrate` layer order
|
|
169
|
+
and named dependency results make dynamic plans easier to replay, but arbitrary
|
|
170
|
+
branch/loop call counts can still invalidate the positional prefix. Never use
|
|
171
|
+
`Date.now()`, `Math.random()`, or no-argument `new Date()` in a script.
|
|
172
|
+
|
|
173
|
+
Before shipping a workflow change:
|
|
174
|
+
|
|
175
|
+
- read [lifecycle](references/lifecycle.md) for pause, stop, budget, and resume;
|
|
176
|
+
- read [debugging](references/debugging.md) when reproducing a failure;
|
|
177
|
+
- read [the review checklist](references/review.md) before accepting topology;
|
|
178
|
+
- read [the generated capability index](references/capabilities.md) when a
|
|
179
|
+
signature, default, or support boundary is disputed; remember that a tier
|
|
180
|
+
names a key in the host catalogue while model/thinking resolution stays in
|
|
181
|
+
pi-subagents;
|
|
182
|
+
- ensure every package-relative example/reference link remains inside the
|
|
183
|
+
publishable package.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export const meta = {
|
|
2
|
+
name: "named-task-graph",
|
|
3
|
+
description: "Run independent research and synthesize it through named dependencies",
|
|
4
|
+
phases: [{ title: "research" }, { title: "synthesis" }],
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
const graph = await orchestrate([
|
|
8
|
+
{
|
|
9
|
+
id: "architecture",
|
|
10
|
+
phase: "research",
|
|
11
|
+
description: "Inspect the architecture",
|
|
12
|
+
run: () => agent("Inspect the architecture and report concrete design constraints", { label: "architecture" }),
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
id: "risk",
|
|
16
|
+
phase: "research",
|
|
17
|
+
description: "Find operational risks",
|
|
18
|
+
run: () => agent("Find operational and recovery risks", { label: "risk" }),
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: "synthesis",
|
|
22
|
+
phase: "synthesis",
|
|
23
|
+
dependsOn: ["architecture", "risk"],
|
|
24
|
+
description: "Combine the named findings",
|
|
25
|
+
run: ({ results, statuses }) =>
|
|
26
|
+
agent(
|
|
27
|
+
"Combine these findings. Mention any unavailable dependency explicitly.\n\n" +
|
|
28
|
+
JSON.stringify({ results, statuses }),
|
|
29
|
+
{ label: "synthesis" },
|
|
30
|
+
),
|
|
31
|
+
},
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
return graph;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<!-- GENERATED from WORKFLOW_CAPABILITY_CONTRACT; do not edit by hand. -->
|
|
2
2
|
# Workflow capability index
|
|
3
3
|
|
|
4
|
-
Contract format: `
|
|
5
|
-
Contract content / skill / extension: `1.
|
|
4
|
+
Contract format: `2.0.0`<br>
|
|
5
|
+
Contract content / skill / extension: `1.5.0`
|
|
6
6
|
|
|
7
7
|
This compact generated index covers supported runtime globals and workflow-tool inputs. For constraints, compatibility behavior, internal boundaries, and dynamic-reference ownership, follow the [exhaustive generated facts](capability-details.md).
|
|
8
8
|
|
|
@@ -11,9 +11,10 @@ This compact generated index covers supported runtime globals and workflow-tool
|
|
|
11
11
|
<!-- BEGIN GENERATED SUPPORTED WORKFLOW CAPABILITIES -->
|
|
12
12
|
| Name | Classification | Signature | Options and defaults |
|
|
13
13
|
| --- | --- | --- | --- |
|
|
14
|
-
| agent | runtime-global | `agent(prompt, options?) => Promise<string \| structured value \| null>` | `label`: string (optional); default: derived from phase and call count<br>`phase`: string (optional); default: current phase<br>`schema`: plain JSON Schema (optional)<br>`
|
|
14
|
+
| agent | runtime-global | `agent(prompt, options?) => Promise<string \| structured value \| null>` | `label`: string (optional); default: derived from phase and call count<br>`phase`: string (optional); default: current phase<br>`schema`: plain JSON Schema (optional)<br>`tier`: agent tier key (optional); default: the agent's own tier, else agentTiers.defaultTier<br>`isolation`: "worktree" (optional); default: pi-subagents worktree policy<br>`thread`: string (optional); default: fresh session per call<br>`toolset`: string (optional); default: configured host/agent toolset hint<br>`excludeTools`: string[] (optional)<br>`agentType`: string (optional); default: general-purpose<br>`timeoutMs`: number \| null (optional); default: 300000 (5min); null disables<br>`retries`: number (optional); default: run retry count |
|
|
15
15
|
| parallel | runtime-global | `parallel(thunks) => Promise<Array<unknown \| null>>` | — |
|
|
16
16
|
| pipeline | runtime-global | `pipeline(items, ...stages) => Promise<Array<unknown \| null>>` | — |
|
|
17
|
+
| orchestrate | runtime-global | `orchestrate(tasks, options?) => Promise<{ results, tasks }>` | `onError`: "skip-dependents"\|"continue"\|"fail-fast" (optional); default: "skip-dependents" |
|
|
17
18
|
| workflow | runtime-global | `workflow(savedName, childArgs?) => Promise<unknown>` | — |
|
|
18
19
|
| verify | runtime-global | `verify(item, options?) => Promise<{ real, realCount, total, votes }>` | `reviewers`: number (optional); default: 2<br>`threshold`: number (optional); default: 0.5<br>`lens`: string \| string[] (optional) |
|
|
19
20
|
| judgePanel | runtime-global | `judgePanel(attempts, options?) => Promise<{ index, attempt, score, judgments } \| undefined>` | `judges`: number (optional); default: 3<br>`rubric`: string (optional); default: overall quality and correctness |
|